Add Slice 4 tuner stack with AGENTS-compliant core types and HTTP API.
Introduces Si4684/ADAU1701 drivers, TunerService, FrequencyKHz, SeekDirection, /api/tuner routes without file-scope globals, host tests, and firmware blob tooling (binaries remain gitignored). Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@@ -15,3 +15,9 @@ docs/api/
|
|||||||
.vscode/
|
.vscode/
|
||||||
.idea/
|
.idea/
|
||||||
.DS_Store
|
.DS_Store
|
||||||
|
__pycache__/
|
||||||
|
*.pyc
|
||||||
|
|
||||||
|
# Proprietary Si4684 firmware blobs (Skyworks / uGreen — local only, not in git)
|
||||||
|
Firmware/Si4684-Firmware/*.bin
|
||||||
|
Firmware/Si4684-Firmware/.ugreen-extract-*/
|
||||||
|
|||||||
@@ -3,7 +3,7 @@ cmake_minimum_required(VERSION 3.16)
|
|||||||
|
|
||||||
include($ENV{IDF_PATH}/tools/cmake/project.cmake)
|
include($ENV{IDF_PATH}/tools/cmake/project.cmake)
|
||||||
|
|
||||||
# Nested driver components live under components/drivers/
|
# Nested driver/service components live under components/
|
||||||
set(EXTRA_COMPONENT_DIRS "components/drivers")
|
set(EXTRA_COMPONENT_DIRS "components/drivers" "components/services")
|
||||||
|
|
||||||
project(digiradio)
|
project(digiradio)
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because one or more lines are too long
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,443 @@
|
|||||||
|
/*
|
||||||
|
* File: C:\Users\mikbi\Documents\Allegati\Desktop\DigiRadio\DigiRadio_IC_1_PARAM.h
|
||||||
|
*
|
||||||
|
* Created: Monday, July 6, 2026 2:41:28 PM
|
||||||
|
* Description: DigiRadio:IC 1 parameter RAM definitions.
|
||||||
|
*
|
||||||
|
* This software is distributed in the hope that it will be useful,
|
||||||
|
* but is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR
|
||||||
|
* CONDITIONS OF ANY KIND, without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
|
||||||
|
*
|
||||||
|
* This software may only be used to program products purchased from
|
||||||
|
* Analog Devices for incorporation by you into audio products that
|
||||||
|
* are intended for resale to audio product end users. This software
|
||||||
|
* may not be distributed whole or in any part to third parties.
|
||||||
|
*
|
||||||
|
* Copyright ©2026 Analog Devices, Inc. All rights reserved.
|
||||||
|
*/
|
||||||
|
#ifndef __DIGIRADIO_IC_1_PARAM_H__
|
||||||
|
#define __DIGIRADIO_IC_1_PARAM_H__
|
||||||
|
|
||||||
|
|
||||||
|
/* Module Si4674 - Multiple Volume Control*/
|
||||||
|
#define COUNT_SI4674 2
|
||||||
|
#define DEVICE_SI4674 "IC1"
|
||||||
|
#define ADDR_SI4674 0
|
||||||
|
#define FIXPT_SI4674 0x00800000
|
||||||
|
#define VALUE_SI4674 SIGMASTUDIOTYPE_FIXPOINT_CONVERT(1)
|
||||||
|
#define TYPE_SI4674 SIGMASTUDIOTYPE_FIXPOINT
|
||||||
|
#define ADDR_SI4674_1 1
|
||||||
|
#define FIXPT_SI4674_1 0x00800000
|
||||||
|
#define VALUE_SI4674_1 SIGMASTUDIOTYPE_FIXPOINT_CONVERT(1)
|
||||||
|
#define TYPE_SI4674_1 SIGMASTUDIOTYPE_FIXPOINT
|
||||||
|
|
||||||
|
/* Module ESP32 - Multiple Volume Control*/
|
||||||
|
#define COUNT_ESP32 2
|
||||||
|
#define DEVICE_ESP32 "IC1"
|
||||||
|
#define ADDR_ESP32 2
|
||||||
|
#define FIXPT_ESP32 0x00800000
|
||||||
|
#define VALUE_ESP32 SIGMASTUDIOTYPE_FIXPOINT_CONVERT(1)
|
||||||
|
#define TYPE_ESP32 SIGMASTUDIOTYPE_FIXPOINT
|
||||||
|
#define ADDR_ESP32_1 3
|
||||||
|
#define FIXPT_ESP32_1 0x00800000
|
||||||
|
#define VALUE_ESP32_1 SIGMASTUDIOTYPE_FIXPOINT_CONVERT(1)
|
||||||
|
#define TYPE_ESP32_1 SIGMASTUDIOTYPE_FIXPOINT
|
||||||
|
|
||||||
|
/* Module St Mixer1 - Stereo Mixer*/
|
||||||
|
#define COUNT_STMIXER1 2
|
||||||
|
#define DEVICE_STMIXER1 "IC1"
|
||||||
|
#define ADDR_STMIXER1_ST0_VOLUME 4
|
||||||
|
#define FIXPT_STMIXER1_ST0_VOLUME 0x00800000
|
||||||
|
#define VALUE_STMIXER1_ST0_VOLUME SIGMASTUDIOTYPE_FIXPOINT_CONVERT(1)
|
||||||
|
#define TYPE_STMIXER1_ST0_VOLUME SIGMASTUDIOTYPE_FIXPOINT
|
||||||
|
#define ADDR_STMIXER1_ST1_VOLUME 5
|
||||||
|
#define FIXPT_STMIXER1_ST1_VOLUME 0x00800000
|
||||||
|
#define VALUE_STMIXER1_ST1_VOLUME SIGMASTUDIOTYPE_FIXPOINT_CONVERT(1)
|
||||||
|
#define TYPE_STMIXER1_ST1_VOLUME SIGMASTUDIOTYPE_FIXPOINT
|
||||||
|
|
||||||
|
/* Module Param EQ1 - Parametric EQ*/
|
||||||
|
#define COUNT_PARAMEQ1 60
|
||||||
|
#define DEVICE_PARAMEQ1 "IC1"
|
||||||
|
#define ADDR_PARAMEQ1_ST0_B0 6
|
||||||
|
#define FIXPT_PARAMEQ1_ST0_B0 0x007FC365
|
||||||
|
#define VALUE_PARAMEQ1_ST0_B0 SIGMASTUDIOTYPE_FIXPOINT_CONVERT(0.998150511190452)
|
||||||
|
#define TYPE_PARAMEQ1_ST0_B0 SIGMASTUDIOTYPE_FIXPOINT
|
||||||
|
#define ADDR_PARAMEQ1_ST0_B0 6
|
||||||
|
#define FIXPT_PARAMEQ1_ST0_B0 0x007FC365
|
||||||
|
#define VALUE_PARAMEQ1_ST0_B0 SIGMASTUDIOTYPE_FIXPOINT_CONVERT(0.998150511190452)
|
||||||
|
#define TYPE_PARAMEQ1_ST0_B0 SIGMASTUDIOTYPE_FIXPOINT
|
||||||
|
#define ADDR_PARAMEQ1_ST0_B1 7
|
||||||
|
#define FIXPT_PARAMEQ1_ST0_B1 0xFF007936
|
||||||
|
#define VALUE_PARAMEQ1_ST0_B1 SIGMASTUDIOTYPE_FIXPOINT_CONVERT(-1.9963010223809)
|
||||||
|
#define TYPE_PARAMEQ1_ST0_B1 SIGMASTUDIOTYPE_FIXPOINT
|
||||||
|
#define ADDR_PARAMEQ1_ST0_B1 7
|
||||||
|
#define FIXPT_PARAMEQ1_ST0_B1 0xFF007936
|
||||||
|
#define VALUE_PARAMEQ1_ST0_B1 SIGMASTUDIOTYPE_FIXPOINT_CONVERT(-1.9963010223809)
|
||||||
|
#define TYPE_PARAMEQ1_ST0_B1 SIGMASTUDIOTYPE_FIXPOINT
|
||||||
|
#define ADDR_PARAMEQ1_ST0_B2 8
|
||||||
|
#define FIXPT_PARAMEQ1_ST0_B2 0x007FC365
|
||||||
|
#define VALUE_PARAMEQ1_ST0_B2 SIGMASTUDIOTYPE_FIXPOINT_CONVERT(0.998150511190452)
|
||||||
|
#define TYPE_PARAMEQ1_ST0_B2 SIGMASTUDIOTYPE_FIXPOINT
|
||||||
|
#define ADDR_PARAMEQ1_ST0_B2 8
|
||||||
|
#define FIXPT_PARAMEQ1_ST0_B2 0x007FC365
|
||||||
|
#define VALUE_PARAMEQ1_ST0_B2 SIGMASTUDIOTYPE_FIXPOINT_CONVERT(0.998150511190452)
|
||||||
|
#define TYPE_PARAMEQ1_ST0_B2 SIGMASTUDIOTYPE_FIXPOINT
|
||||||
|
#define ADDR_PARAMEQ1_ST0_A0 9
|
||||||
|
#define FIXPT_PARAMEQ1_ST0_A0 0x00FF86AE
|
||||||
|
#define VALUE_PARAMEQ1_ST0_A0 SIGMASTUDIOTYPE_FIXPOINT_CONVERT(1.99629760176912)
|
||||||
|
#define TYPE_PARAMEQ1_ST0_A0 SIGMASTUDIOTYPE_FIXPOINT
|
||||||
|
#define ADDR_PARAMEQ1_ST0_A0 9
|
||||||
|
#define FIXPT_PARAMEQ1_ST0_A0 0x00FF86AE
|
||||||
|
#define VALUE_PARAMEQ1_ST0_A0 SIGMASTUDIOTYPE_FIXPOINT_CONVERT(1.99629760176912)
|
||||||
|
#define TYPE_PARAMEQ1_ST0_A0 SIGMASTUDIOTYPE_FIXPOINT
|
||||||
|
#define ADDR_PARAMEQ1_ST0_A1 10
|
||||||
|
#define FIXPT_PARAMEQ1_ST0_A1 0xFF807919
|
||||||
|
#define VALUE_PARAMEQ1_ST0_A1 SIGMASTUDIOTYPE_FIXPOINT_CONVERT(-0.996304442992686)
|
||||||
|
#define TYPE_PARAMEQ1_ST0_A1 SIGMASTUDIOTYPE_FIXPOINT
|
||||||
|
#define ADDR_PARAMEQ1_ST0_A1 10
|
||||||
|
#define FIXPT_PARAMEQ1_ST0_A1 0xFF807919
|
||||||
|
#define VALUE_PARAMEQ1_ST0_A1 SIGMASTUDIOTYPE_FIXPOINT_CONVERT(-0.996304442992686)
|
||||||
|
#define TYPE_PARAMEQ1_ST0_A1 SIGMASTUDIOTYPE_FIXPOINT
|
||||||
|
#define ADDR_PARAMEQ1_ST1_B0 11
|
||||||
|
#define FIXPT_PARAMEQ1_ST1_B0 0x00800000
|
||||||
|
#define VALUE_PARAMEQ1_ST1_B0 SIGMASTUDIOTYPE_FIXPOINT_CONVERT(1)
|
||||||
|
#define TYPE_PARAMEQ1_ST1_B0 SIGMASTUDIOTYPE_FIXPOINT
|
||||||
|
#define ADDR_PARAMEQ1_ST1_B0 11
|
||||||
|
#define FIXPT_PARAMEQ1_ST1_B0 0x00800000
|
||||||
|
#define VALUE_PARAMEQ1_ST1_B0 SIGMASTUDIOTYPE_FIXPOINT_CONVERT(1)
|
||||||
|
#define TYPE_PARAMEQ1_ST1_B0 SIGMASTUDIOTYPE_FIXPOINT
|
||||||
|
#define ADDR_PARAMEQ1_ST1_B1 12
|
||||||
|
#define FIXPT_PARAMEQ1_ST1_B1 0x00000000
|
||||||
|
#define VALUE_PARAMEQ1_ST1_B1 SIGMASTUDIOTYPE_FIXPOINT_CONVERT(0)
|
||||||
|
#define TYPE_PARAMEQ1_ST1_B1 SIGMASTUDIOTYPE_FIXPOINT
|
||||||
|
#define ADDR_PARAMEQ1_ST1_B1 12
|
||||||
|
#define FIXPT_PARAMEQ1_ST1_B1 0x00000000
|
||||||
|
#define VALUE_PARAMEQ1_ST1_B1 SIGMASTUDIOTYPE_FIXPOINT_CONVERT(0)
|
||||||
|
#define TYPE_PARAMEQ1_ST1_B1 SIGMASTUDIOTYPE_FIXPOINT
|
||||||
|
#define ADDR_PARAMEQ1_ST1_B2 13
|
||||||
|
#define FIXPT_PARAMEQ1_ST1_B2 0x00000000
|
||||||
|
#define VALUE_PARAMEQ1_ST1_B2 SIGMASTUDIOTYPE_FIXPOINT_CONVERT(0)
|
||||||
|
#define TYPE_PARAMEQ1_ST1_B2 SIGMASTUDIOTYPE_FIXPOINT
|
||||||
|
#define ADDR_PARAMEQ1_ST1_B2 13
|
||||||
|
#define FIXPT_PARAMEQ1_ST1_B2 0x00000000
|
||||||
|
#define VALUE_PARAMEQ1_ST1_B2 SIGMASTUDIOTYPE_FIXPOINT_CONVERT(0)
|
||||||
|
#define TYPE_PARAMEQ1_ST1_B2 SIGMASTUDIOTYPE_FIXPOINT
|
||||||
|
#define ADDR_PARAMEQ1_ST1_A0 14
|
||||||
|
#define FIXPT_PARAMEQ1_ST1_A0 0x00000000
|
||||||
|
#define VALUE_PARAMEQ1_ST1_A0 SIGMASTUDIOTYPE_FIXPOINT_CONVERT(0)
|
||||||
|
#define TYPE_PARAMEQ1_ST1_A0 SIGMASTUDIOTYPE_FIXPOINT
|
||||||
|
#define ADDR_PARAMEQ1_ST1_A0 14
|
||||||
|
#define FIXPT_PARAMEQ1_ST1_A0 0x00000000
|
||||||
|
#define VALUE_PARAMEQ1_ST1_A0 SIGMASTUDIOTYPE_FIXPOINT_CONVERT(0)
|
||||||
|
#define TYPE_PARAMEQ1_ST1_A0 SIGMASTUDIOTYPE_FIXPOINT
|
||||||
|
#define ADDR_PARAMEQ1_ST1_A1 15
|
||||||
|
#define FIXPT_PARAMEQ1_ST1_A1 0x00000000
|
||||||
|
#define VALUE_PARAMEQ1_ST1_A1 SIGMASTUDIOTYPE_FIXPOINT_CONVERT(0)
|
||||||
|
#define TYPE_PARAMEQ1_ST1_A1 SIGMASTUDIOTYPE_FIXPOINT
|
||||||
|
#define ADDR_PARAMEQ1_ST1_A1 15
|
||||||
|
#define FIXPT_PARAMEQ1_ST1_A1 0x00000000
|
||||||
|
#define VALUE_PARAMEQ1_ST1_A1 SIGMASTUDIOTYPE_FIXPOINT_CONVERT(0)
|
||||||
|
#define TYPE_PARAMEQ1_ST1_A1 SIGMASTUDIOTYPE_FIXPOINT
|
||||||
|
#define ADDR_PARAMEQ1_ST2_B0 16
|
||||||
|
#define FIXPT_PARAMEQ1_ST2_B0 0x00800000
|
||||||
|
#define VALUE_PARAMEQ1_ST2_B0 SIGMASTUDIOTYPE_FIXPOINT_CONVERT(1)
|
||||||
|
#define TYPE_PARAMEQ1_ST2_B0 SIGMASTUDIOTYPE_FIXPOINT
|
||||||
|
#define ADDR_PARAMEQ1_ST2_B0 16
|
||||||
|
#define FIXPT_PARAMEQ1_ST2_B0 0x00800000
|
||||||
|
#define VALUE_PARAMEQ1_ST2_B0 SIGMASTUDIOTYPE_FIXPOINT_CONVERT(1)
|
||||||
|
#define TYPE_PARAMEQ1_ST2_B0 SIGMASTUDIOTYPE_FIXPOINT
|
||||||
|
#define ADDR_PARAMEQ1_ST2_B1 17
|
||||||
|
#define FIXPT_PARAMEQ1_ST2_B1 0x00000000
|
||||||
|
#define VALUE_PARAMEQ1_ST2_B1 SIGMASTUDIOTYPE_FIXPOINT_CONVERT(0)
|
||||||
|
#define TYPE_PARAMEQ1_ST2_B1 SIGMASTUDIOTYPE_FIXPOINT
|
||||||
|
#define ADDR_PARAMEQ1_ST2_B1 17
|
||||||
|
#define FIXPT_PARAMEQ1_ST2_B1 0x00000000
|
||||||
|
#define VALUE_PARAMEQ1_ST2_B1 SIGMASTUDIOTYPE_FIXPOINT_CONVERT(0)
|
||||||
|
#define TYPE_PARAMEQ1_ST2_B1 SIGMASTUDIOTYPE_FIXPOINT
|
||||||
|
#define ADDR_PARAMEQ1_ST2_B2 18
|
||||||
|
#define FIXPT_PARAMEQ1_ST2_B2 0x00000000
|
||||||
|
#define VALUE_PARAMEQ1_ST2_B2 SIGMASTUDIOTYPE_FIXPOINT_CONVERT(0)
|
||||||
|
#define TYPE_PARAMEQ1_ST2_B2 SIGMASTUDIOTYPE_FIXPOINT
|
||||||
|
#define ADDR_PARAMEQ1_ST2_B2 18
|
||||||
|
#define FIXPT_PARAMEQ1_ST2_B2 0x00000000
|
||||||
|
#define VALUE_PARAMEQ1_ST2_B2 SIGMASTUDIOTYPE_FIXPOINT_CONVERT(0)
|
||||||
|
#define TYPE_PARAMEQ1_ST2_B2 SIGMASTUDIOTYPE_FIXPOINT
|
||||||
|
#define ADDR_PARAMEQ1_ST2_A0 19
|
||||||
|
#define FIXPT_PARAMEQ1_ST2_A0 0x00000000
|
||||||
|
#define VALUE_PARAMEQ1_ST2_A0 SIGMASTUDIOTYPE_FIXPOINT_CONVERT(0)
|
||||||
|
#define TYPE_PARAMEQ1_ST2_A0 SIGMASTUDIOTYPE_FIXPOINT
|
||||||
|
#define ADDR_PARAMEQ1_ST2_A0 19
|
||||||
|
#define FIXPT_PARAMEQ1_ST2_A0 0x00000000
|
||||||
|
#define VALUE_PARAMEQ1_ST2_A0 SIGMASTUDIOTYPE_FIXPOINT_CONVERT(0)
|
||||||
|
#define TYPE_PARAMEQ1_ST2_A0 SIGMASTUDIOTYPE_FIXPOINT
|
||||||
|
#define ADDR_PARAMEQ1_ST2_A1 20
|
||||||
|
#define FIXPT_PARAMEQ1_ST2_A1 0x00000000
|
||||||
|
#define VALUE_PARAMEQ1_ST2_A1 SIGMASTUDIOTYPE_FIXPOINT_CONVERT(0)
|
||||||
|
#define TYPE_PARAMEQ1_ST2_A1 SIGMASTUDIOTYPE_FIXPOINT
|
||||||
|
#define ADDR_PARAMEQ1_ST2_A1 20
|
||||||
|
#define FIXPT_PARAMEQ1_ST2_A1 0x00000000
|
||||||
|
#define VALUE_PARAMEQ1_ST2_A1 SIGMASTUDIOTYPE_FIXPOINT_CONVERT(0)
|
||||||
|
#define TYPE_PARAMEQ1_ST2_A1 SIGMASTUDIOTYPE_FIXPOINT
|
||||||
|
#define ADDR_PARAMEQ1_ST3_B0 21
|
||||||
|
#define FIXPT_PARAMEQ1_ST3_B0 0x00800000
|
||||||
|
#define VALUE_PARAMEQ1_ST3_B0 SIGMASTUDIOTYPE_FIXPOINT_CONVERT(1)
|
||||||
|
#define TYPE_PARAMEQ1_ST3_B0 SIGMASTUDIOTYPE_FIXPOINT
|
||||||
|
#define ADDR_PARAMEQ1_ST3_B0 21
|
||||||
|
#define FIXPT_PARAMEQ1_ST3_B0 0x00800000
|
||||||
|
#define VALUE_PARAMEQ1_ST3_B0 SIGMASTUDIOTYPE_FIXPOINT_CONVERT(1)
|
||||||
|
#define TYPE_PARAMEQ1_ST3_B0 SIGMASTUDIOTYPE_FIXPOINT
|
||||||
|
#define ADDR_PARAMEQ1_ST3_B1 22
|
||||||
|
#define FIXPT_PARAMEQ1_ST3_B1 0x00000000
|
||||||
|
#define VALUE_PARAMEQ1_ST3_B1 SIGMASTUDIOTYPE_FIXPOINT_CONVERT(0)
|
||||||
|
#define TYPE_PARAMEQ1_ST3_B1 SIGMASTUDIOTYPE_FIXPOINT
|
||||||
|
#define ADDR_PARAMEQ1_ST3_B1 22
|
||||||
|
#define FIXPT_PARAMEQ1_ST3_B1 0x00000000
|
||||||
|
#define VALUE_PARAMEQ1_ST3_B1 SIGMASTUDIOTYPE_FIXPOINT_CONVERT(0)
|
||||||
|
#define TYPE_PARAMEQ1_ST3_B1 SIGMASTUDIOTYPE_FIXPOINT
|
||||||
|
#define ADDR_PARAMEQ1_ST3_B2 23
|
||||||
|
#define FIXPT_PARAMEQ1_ST3_B2 0x00000000
|
||||||
|
#define VALUE_PARAMEQ1_ST3_B2 SIGMASTUDIOTYPE_FIXPOINT_CONVERT(0)
|
||||||
|
#define TYPE_PARAMEQ1_ST3_B2 SIGMASTUDIOTYPE_FIXPOINT
|
||||||
|
#define ADDR_PARAMEQ1_ST3_B2 23
|
||||||
|
#define FIXPT_PARAMEQ1_ST3_B2 0x00000000
|
||||||
|
#define VALUE_PARAMEQ1_ST3_B2 SIGMASTUDIOTYPE_FIXPOINT_CONVERT(0)
|
||||||
|
#define TYPE_PARAMEQ1_ST3_B2 SIGMASTUDIOTYPE_FIXPOINT
|
||||||
|
#define ADDR_PARAMEQ1_ST3_A0 24
|
||||||
|
#define FIXPT_PARAMEQ1_ST3_A0 0x00000000
|
||||||
|
#define VALUE_PARAMEQ1_ST3_A0 SIGMASTUDIOTYPE_FIXPOINT_CONVERT(0)
|
||||||
|
#define TYPE_PARAMEQ1_ST3_A0 SIGMASTUDIOTYPE_FIXPOINT
|
||||||
|
#define ADDR_PARAMEQ1_ST3_A0 24
|
||||||
|
#define FIXPT_PARAMEQ1_ST3_A0 0x00000000
|
||||||
|
#define VALUE_PARAMEQ1_ST3_A0 SIGMASTUDIOTYPE_FIXPOINT_CONVERT(0)
|
||||||
|
#define TYPE_PARAMEQ1_ST3_A0 SIGMASTUDIOTYPE_FIXPOINT
|
||||||
|
#define ADDR_PARAMEQ1_ST3_A1 25
|
||||||
|
#define FIXPT_PARAMEQ1_ST3_A1 0x00000000
|
||||||
|
#define VALUE_PARAMEQ1_ST3_A1 SIGMASTUDIOTYPE_FIXPOINT_CONVERT(0)
|
||||||
|
#define TYPE_PARAMEQ1_ST3_A1 SIGMASTUDIOTYPE_FIXPOINT
|
||||||
|
#define ADDR_PARAMEQ1_ST3_A1 25
|
||||||
|
#define FIXPT_PARAMEQ1_ST3_A1 0x00000000
|
||||||
|
#define VALUE_PARAMEQ1_ST3_A1 SIGMASTUDIOTYPE_FIXPOINT_CONVERT(0)
|
||||||
|
#define TYPE_PARAMEQ1_ST3_A1 SIGMASTUDIOTYPE_FIXPOINT
|
||||||
|
#define ADDR_PARAMEQ1_ST4_B0 26
|
||||||
|
#define FIXPT_PARAMEQ1_ST4_B0 0x00800000
|
||||||
|
#define VALUE_PARAMEQ1_ST4_B0 SIGMASTUDIOTYPE_FIXPOINT_CONVERT(1)
|
||||||
|
#define TYPE_PARAMEQ1_ST4_B0 SIGMASTUDIOTYPE_FIXPOINT
|
||||||
|
#define ADDR_PARAMEQ1_ST4_B0 26
|
||||||
|
#define FIXPT_PARAMEQ1_ST4_B0 0x00800000
|
||||||
|
#define VALUE_PARAMEQ1_ST4_B0 SIGMASTUDIOTYPE_FIXPOINT_CONVERT(1)
|
||||||
|
#define TYPE_PARAMEQ1_ST4_B0 SIGMASTUDIOTYPE_FIXPOINT
|
||||||
|
#define ADDR_PARAMEQ1_ST4_B1 27
|
||||||
|
#define FIXPT_PARAMEQ1_ST4_B1 0x00000000
|
||||||
|
#define VALUE_PARAMEQ1_ST4_B1 SIGMASTUDIOTYPE_FIXPOINT_CONVERT(0)
|
||||||
|
#define TYPE_PARAMEQ1_ST4_B1 SIGMASTUDIOTYPE_FIXPOINT
|
||||||
|
#define ADDR_PARAMEQ1_ST4_B1 27
|
||||||
|
#define FIXPT_PARAMEQ1_ST4_B1 0x00000000
|
||||||
|
#define VALUE_PARAMEQ1_ST4_B1 SIGMASTUDIOTYPE_FIXPOINT_CONVERT(0)
|
||||||
|
#define TYPE_PARAMEQ1_ST4_B1 SIGMASTUDIOTYPE_FIXPOINT
|
||||||
|
#define ADDR_PARAMEQ1_ST4_B2 28
|
||||||
|
#define FIXPT_PARAMEQ1_ST4_B2 0x00000000
|
||||||
|
#define VALUE_PARAMEQ1_ST4_B2 SIGMASTUDIOTYPE_FIXPOINT_CONVERT(0)
|
||||||
|
#define TYPE_PARAMEQ1_ST4_B2 SIGMASTUDIOTYPE_FIXPOINT
|
||||||
|
#define ADDR_PARAMEQ1_ST4_B2 28
|
||||||
|
#define FIXPT_PARAMEQ1_ST4_B2 0x00000000
|
||||||
|
#define VALUE_PARAMEQ1_ST4_B2 SIGMASTUDIOTYPE_FIXPOINT_CONVERT(0)
|
||||||
|
#define TYPE_PARAMEQ1_ST4_B2 SIGMASTUDIOTYPE_FIXPOINT
|
||||||
|
#define ADDR_PARAMEQ1_ST4_A0 29
|
||||||
|
#define FIXPT_PARAMEQ1_ST4_A0 0x00000000
|
||||||
|
#define VALUE_PARAMEQ1_ST4_A0 SIGMASTUDIOTYPE_FIXPOINT_CONVERT(0)
|
||||||
|
#define TYPE_PARAMEQ1_ST4_A0 SIGMASTUDIOTYPE_FIXPOINT
|
||||||
|
#define ADDR_PARAMEQ1_ST4_A0 29
|
||||||
|
#define FIXPT_PARAMEQ1_ST4_A0 0x00000000
|
||||||
|
#define VALUE_PARAMEQ1_ST4_A0 SIGMASTUDIOTYPE_FIXPOINT_CONVERT(0)
|
||||||
|
#define TYPE_PARAMEQ1_ST4_A0 SIGMASTUDIOTYPE_FIXPOINT
|
||||||
|
#define ADDR_PARAMEQ1_ST4_A1 30
|
||||||
|
#define FIXPT_PARAMEQ1_ST4_A1 0x00000000
|
||||||
|
#define VALUE_PARAMEQ1_ST4_A1 SIGMASTUDIOTYPE_FIXPOINT_CONVERT(0)
|
||||||
|
#define TYPE_PARAMEQ1_ST4_A1 SIGMASTUDIOTYPE_FIXPOINT
|
||||||
|
#define ADDR_PARAMEQ1_ST4_A1 30
|
||||||
|
#define FIXPT_PARAMEQ1_ST4_A1 0x00000000
|
||||||
|
#define VALUE_PARAMEQ1_ST4_A1 SIGMASTUDIOTYPE_FIXPOINT_CONVERT(0)
|
||||||
|
#define TYPE_PARAMEQ1_ST4_A1 SIGMASTUDIOTYPE_FIXPOINT
|
||||||
|
#define ADDR_PARAMEQ1_ST5_B0 31
|
||||||
|
#define FIXPT_PARAMEQ1_ST5_B0 0x00800000
|
||||||
|
#define VALUE_PARAMEQ1_ST5_B0 SIGMASTUDIOTYPE_FIXPOINT_CONVERT(1)
|
||||||
|
#define TYPE_PARAMEQ1_ST5_B0 SIGMASTUDIOTYPE_FIXPOINT
|
||||||
|
#define ADDR_PARAMEQ1_ST5_B0 31
|
||||||
|
#define FIXPT_PARAMEQ1_ST5_B0 0x00800000
|
||||||
|
#define VALUE_PARAMEQ1_ST5_B0 SIGMASTUDIOTYPE_FIXPOINT_CONVERT(1)
|
||||||
|
#define TYPE_PARAMEQ1_ST5_B0 SIGMASTUDIOTYPE_FIXPOINT
|
||||||
|
#define ADDR_PARAMEQ1_ST5_B1 32
|
||||||
|
#define FIXPT_PARAMEQ1_ST5_B1 0x00000000
|
||||||
|
#define VALUE_PARAMEQ1_ST5_B1 SIGMASTUDIOTYPE_FIXPOINT_CONVERT(0)
|
||||||
|
#define TYPE_PARAMEQ1_ST5_B1 SIGMASTUDIOTYPE_FIXPOINT
|
||||||
|
#define ADDR_PARAMEQ1_ST5_B1 32
|
||||||
|
#define FIXPT_PARAMEQ1_ST5_B1 0x00000000
|
||||||
|
#define VALUE_PARAMEQ1_ST5_B1 SIGMASTUDIOTYPE_FIXPOINT_CONVERT(0)
|
||||||
|
#define TYPE_PARAMEQ1_ST5_B1 SIGMASTUDIOTYPE_FIXPOINT
|
||||||
|
#define ADDR_PARAMEQ1_ST5_B2 33
|
||||||
|
#define FIXPT_PARAMEQ1_ST5_B2 0x00000000
|
||||||
|
#define VALUE_PARAMEQ1_ST5_B2 SIGMASTUDIOTYPE_FIXPOINT_CONVERT(0)
|
||||||
|
#define TYPE_PARAMEQ1_ST5_B2 SIGMASTUDIOTYPE_FIXPOINT
|
||||||
|
#define ADDR_PARAMEQ1_ST5_B2 33
|
||||||
|
#define FIXPT_PARAMEQ1_ST5_B2 0x00000000
|
||||||
|
#define VALUE_PARAMEQ1_ST5_B2 SIGMASTUDIOTYPE_FIXPOINT_CONVERT(0)
|
||||||
|
#define TYPE_PARAMEQ1_ST5_B2 SIGMASTUDIOTYPE_FIXPOINT
|
||||||
|
#define ADDR_PARAMEQ1_ST5_A0 34
|
||||||
|
#define FIXPT_PARAMEQ1_ST5_A0 0x00000000
|
||||||
|
#define VALUE_PARAMEQ1_ST5_A0 SIGMASTUDIOTYPE_FIXPOINT_CONVERT(0)
|
||||||
|
#define TYPE_PARAMEQ1_ST5_A0 SIGMASTUDIOTYPE_FIXPOINT
|
||||||
|
#define ADDR_PARAMEQ1_ST5_A0 34
|
||||||
|
#define FIXPT_PARAMEQ1_ST5_A0 0x00000000
|
||||||
|
#define VALUE_PARAMEQ1_ST5_A0 SIGMASTUDIOTYPE_FIXPOINT_CONVERT(0)
|
||||||
|
#define TYPE_PARAMEQ1_ST5_A0 SIGMASTUDIOTYPE_FIXPOINT
|
||||||
|
#define ADDR_PARAMEQ1_ST5_A1 35
|
||||||
|
#define FIXPT_PARAMEQ1_ST5_A1 0x00000000
|
||||||
|
#define VALUE_PARAMEQ1_ST5_A1 SIGMASTUDIOTYPE_FIXPOINT_CONVERT(0)
|
||||||
|
#define TYPE_PARAMEQ1_ST5_A1 SIGMASTUDIOTYPE_FIXPOINT
|
||||||
|
#define ADDR_PARAMEQ1_ST5_A1 35
|
||||||
|
#define FIXPT_PARAMEQ1_ST5_A1 0x00000000
|
||||||
|
#define VALUE_PARAMEQ1_ST5_A1 SIGMASTUDIOTYPE_FIXPOINT_CONVERT(0)
|
||||||
|
#define TYPE_PARAMEQ1_ST5_A1 SIGMASTUDIOTYPE_FIXPOINT
|
||||||
|
|
||||||
|
/* Module Multiple 1 - Multiple Volume Control*/
|
||||||
|
#define COUNT_MULTIPLE1 2
|
||||||
|
#define DEVICE_MULTIPLE1 "IC1"
|
||||||
|
#define ADDR_MULTIPLE1 36
|
||||||
|
#define FIXPT_MULTIPLE1 0x00800000
|
||||||
|
#define VALUE_MULTIPLE1 SIGMASTUDIOTYPE_FIXPOINT_CONVERT(1)
|
||||||
|
#define TYPE_MULTIPLE1 SIGMASTUDIOTYPE_FIXPOINT
|
||||||
|
#define ADDR_MULTIPLE1_1 37
|
||||||
|
#define FIXPT_MULTIPLE1_1 0x00800000
|
||||||
|
#define VALUE_MULTIPLE1_1 SIGMASTUDIOTYPE_FIXPOINT_CONVERT(1)
|
||||||
|
#define TYPE_MULTIPLE1_1 SIGMASTUDIOTYPE_FIXPOINT
|
||||||
|
|
||||||
|
/* Module Limiter 1 - Limiter*/
|
||||||
|
#define COUNT_LIMITER1 15
|
||||||
|
#define DEVICE_LIMITER1 "IC1"
|
||||||
|
#define ADDR_LIMITER1_S1 38
|
||||||
|
#define FIXPT_LIMITER1_S1 0xFFFE2BE3
|
||||||
|
#define VALUE_LIMITER1_S1 SIGMASTUDIOTYPE_FIXPOINT_CONVERT(-0.01428571428571)
|
||||||
|
#define TYPE_LIMITER1_S1 SIGMASTUDIOTYPE_FIXPOINT
|
||||||
|
#define ADDR_LIMITER1_INT1 39
|
||||||
|
#define FIXPT_LIMITER1_INT1 0x0022BE2B
|
||||||
|
#define VALUE_LIMITER1_INT1 SIGMASTUDIOTYPE_FIXPOINT_CONVERT(0.27142857142857)
|
||||||
|
#define TYPE_LIMITER1_INT1 SIGMASTUDIOTYPE_FIXPOINT
|
||||||
|
#define ADDR_LIMITER1_S2 40
|
||||||
|
#define FIXPT_LIMITER1_S2 0xFFEAAAAB
|
||||||
|
#define VALUE_LIMITER1_S2 SIGMASTUDIOTYPE_FIXPOINT_CONVERT(-0.16666666666667)
|
||||||
|
#define TYPE_LIMITER1_S2 SIGMASTUDIOTYPE_FIXPOINT
|
||||||
|
#define ADDR_LIMITER1_INT2 41
|
||||||
|
#define FIXPT_LIMITER1_INT2 0x006AAAAA
|
||||||
|
#define VALUE_LIMITER1_INT2 SIGMASTUDIOTYPE_FIXPOINT_CONVERT(0.8333333333333)
|
||||||
|
#define TYPE_LIMITER1_INT2 SIGMASTUDIOTYPE_FIXPOINT
|
||||||
|
#define ADDR_LIMITER1_S3 42
|
||||||
|
#define FIXPT_LIMITER1_S3 0xFF9A69A7
|
||||||
|
#define VALUE_LIMITER1_S3 SIGMASTUDIOTYPE_FIXPOINT_CONVERT(-0.79365079365079)
|
||||||
|
#define TYPE_LIMITER1_S3 SIGMASTUDIOTYPE_FIXPOINT
|
||||||
|
#define ADDR_LIMITER1_INT3 43
|
||||||
|
#define FIXPT_LIMITER1_INT3 0x00FDF7DF
|
||||||
|
#define VALUE_LIMITER1_INT3 SIGMASTUDIOTYPE_FIXPOINT_CONVERT(1.98412698412698)
|
||||||
|
#define TYPE_LIMITER1_INT3 SIGMASTUDIOTYPE_FIXPOINT
|
||||||
|
#define ADDR_LIMITER1_S4 44
|
||||||
|
#define FIXPT_LIMITER1_S4 0xF83C6C20
|
||||||
|
#define VALUE_LIMITER1_S4 SIGMASTUDIOTYPE_FIXPOINT_CONVERT(-15.527950310559)
|
||||||
|
#define TYPE_LIMITER1_S4 SIGMASTUDIOTYPE_FIXPOINT
|
||||||
|
#define ADDR_LIMITER1_INT4 45
|
||||||
|
#define FIXPT_LIMITER1_INT4 0x03F5AA22
|
||||||
|
#define VALUE_LIMITER1_INT4 SIGMASTUDIOTYPE_FIXPOINT_CONVERT(7.91925465838509)
|
||||||
|
#define TYPE_LIMITER1_INT4 SIGMASTUDIOTYPE_FIXPOINT
|
||||||
|
#define ADDR_LIMITER1_C1 46
|
||||||
|
#define FIXPT_LIMITER1_C1 0x01800000
|
||||||
|
#define VALUE_LIMITER1_C1 SIGMASTUDIOTYPE_FIXPOINT_CONVERT(3)
|
||||||
|
#define TYPE_LIMITER1_C1 SIGMASTUDIOTYPE_FIXPOINT
|
||||||
|
#define ADDR_LIMITER1_C2 47
|
||||||
|
#define FIXPT_LIMITER1_C2 0x01000000
|
||||||
|
#define VALUE_LIMITER1_C2 SIGMASTUDIOTYPE_FIXPOINT_CONVERT(2)
|
||||||
|
#define TYPE_LIMITER1_C2 SIGMASTUDIOTYPE_FIXPOINT
|
||||||
|
#define ADDR_LIMITER1_C3 48
|
||||||
|
#define FIXPT_LIMITER1_C3 0x00333333
|
||||||
|
#define VALUE_LIMITER1_C3 SIGMASTUDIOTYPE_FIXPOINT_CONVERT(0.4)
|
||||||
|
#define TYPE_LIMITER1_C3 SIGMASTUDIOTYPE_FIXPOINT
|
||||||
|
#define ADDR_LIMITER1_THRESHOLD 49
|
||||||
|
#define FIXPT_LIMITER1_THRESHOLD 0x00800000
|
||||||
|
#define VALUE_LIMITER1_THRESHOLD SIGMASTUDIOTYPE_FIXPOINT_CONVERT(1)
|
||||||
|
#define TYPE_LIMITER1_THRESHOLD SIGMASTUDIOTYPE_FIXPOINT
|
||||||
|
#define ADDR_LIMITER1_RMS 50
|
||||||
|
#define FIXPT_LIMITER1_RMS 0x000007DC
|
||||||
|
#define VALUE_LIMITER1_RMS SIGMASTUDIOTYPE_FIXPOINT_CONVERT(0.000239881380791562)
|
||||||
|
#define TYPE_LIMITER1_RMS SIGMASTUDIOTYPE_FIXPOINT
|
||||||
|
#define ADDR_LIMITER1_DECAY 51
|
||||||
|
#define FIXPT_LIMITER1_DECAY 0x00000800
|
||||||
|
#define VALUE_LIMITER1_DECAY SIGMASTUDIOTYPE_FIXPOINT_CONVERT(0.000244140625)
|
||||||
|
#define TYPE_LIMITER1_DECAY SIGMASTUDIOTYPE_FIXPOINT
|
||||||
|
#define ADDR_LIMITER1_DECAYCOMPLEMENT 52
|
||||||
|
#define FIXPT_LIMITER1_DECAYCOMPLEMENT 0x007FF000
|
||||||
|
#define VALUE_LIMITER1_DECAYCOMPLEMENT SIGMASTUDIOTYPE_FIXPOINT_CONVERT(0.99951171875)
|
||||||
|
#define TYPE_LIMITER1_DECAYCOMPLEMENT SIGMASTUDIOTYPE_FIXPOINT
|
||||||
|
|
||||||
|
/* Module Limiter 2 - Limiter*/
|
||||||
|
#define COUNT_LIMITER2 15
|
||||||
|
#define DEVICE_LIMITER2 "IC1"
|
||||||
|
#define ADDR_LIMITER2_S1 53
|
||||||
|
#define FIXPT_LIMITER2_S1 0xFFFE2BE3
|
||||||
|
#define VALUE_LIMITER2_S1 SIGMASTUDIOTYPE_FIXPOINT_CONVERT(-0.01428571428571)
|
||||||
|
#define TYPE_LIMITER2_S1 SIGMASTUDIOTYPE_FIXPOINT
|
||||||
|
#define ADDR_LIMITER2_INT1 54
|
||||||
|
#define FIXPT_LIMITER2_INT1 0x0022BE2B
|
||||||
|
#define VALUE_LIMITER2_INT1 SIGMASTUDIOTYPE_FIXPOINT_CONVERT(0.27142857142857)
|
||||||
|
#define TYPE_LIMITER2_INT1 SIGMASTUDIOTYPE_FIXPOINT
|
||||||
|
#define ADDR_LIMITER2_S2 55
|
||||||
|
#define FIXPT_LIMITER2_S2 0xFFEAAAAB
|
||||||
|
#define VALUE_LIMITER2_S2 SIGMASTUDIOTYPE_FIXPOINT_CONVERT(-0.16666666666667)
|
||||||
|
#define TYPE_LIMITER2_S2 SIGMASTUDIOTYPE_FIXPOINT
|
||||||
|
#define ADDR_LIMITER2_INT2 56
|
||||||
|
#define FIXPT_LIMITER2_INT2 0x006AAAAA
|
||||||
|
#define VALUE_LIMITER2_INT2 SIGMASTUDIOTYPE_FIXPOINT_CONVERT(0.8333333333333)
|
||||||
|
#define TYPE_LIMITER2_INT2 SIGMASTUDIOTYPE_FIXPOINT
|
||||||
|
#define ADDR_LIMITER2_S3 57
|
||||||
|
#define FIXPT_LIMITER2_S3 0xFF9A69A7
|
||||||
|
#define VALUE_LIMITER2_S3 SIGMASTUDIOTYPE_FIXPOINT_CONVERT(-0.79365079365079)
|
||||||
|
#define TYPE_LIMITER2_S3 SIGMASTUDIOTYPE_FIXPOINT
|
||||||
|
#define ADDR_LIMITER2_INT3 58
|
||||||
|
#define FIXPT_LIMITER2_INT3 0x00FDF7DF
|
||||||
|
#define VALUE_LIMITER2_INT3 SIGMASTUDIOTYPE_FIXPOINT_CONVERT(1.98412698412698)
|
||||||
|
#define TYPE_LIMITER2_INT3 SIGMASTUDIOTYPE_FIXPOINT
|
||||||
|
#define ADDR_LIMITER2_S4 59
|
||||||
|
#define FIXPT_LIMITER2_S4 0xF83C6C20
|
||||||
|
#define VALUE_LIMITER2_S4 SIGMASTUDIOTYPE_FIXPOINT_CONVERT(-15.527950310559)
|
||||||
|
#define TYPE_LIMITER2_S4 SIGMASTUDIOTYPE_FIXPOINT
|
||||||
|
#define ADDR_LIMITER2_INT4 60
|
||||||
|
#define FIXPT_LIMITER2_INT4 0x03F5AA22
|
||||||
|
#define VALUE_LIMITER2_INT4 SIGMASTUDIOTYPE_FIXPOINT_CONVERT(7.91925465838509)
|
||||||
|
#define TYPE_LIMITER2_INT4 SIGMASTUDIOTYPE_FIXPOINT
|
||||||
|
#define ADDR_LIMITER2_C1 61
|
||||||
|
#define FIXPT_LIMITER2_C1 0x01800000
|
||||||
|
#define VALUE_LIMITER2_C1 SIGMASTUDIOTYPE_FIXPOINT_CONVERT(3)
|
||||||
|
#define TYPE_LIMITER2_C1 SIGMASTUDIOTYPE_FIXPOINT
|
||||||
|
#define ADDR_LIMITER2_C2 62
|
||||||
|
#define FIXPT_LIMITER2_C2 0x01000000
|
||||||
|
#define VALUE_LIMITER2_C2 SIGMASTUDIOTYPE_FIXPOINT_CONVERT(2)
|
||||||
|
#define TYPE_LIMITER2_C2 SIGMASTUDIOTYPE_FIXPOINT
|
||||||
|
#define ADDR_LIMITER2_C3 63
|
||||||
|
#define FIXPT_LIMITER2_C3 0x00333333
|
||||||
|
#define VALUE_LIMITER2_C3 SIGMASTUDIOTYPE_FIXPOINT_CONVERT(0.4)
|
||||||
|
#define TYPE_LIMITER2_C3 SIGMASTUDIOTYPE_FIXPOINT
|
||||||
|
#define ADDR_LIMITER2_THRESHOLD 64
|
||||||
|
#define FIXPT_LIMITER2_THRESHOLD 0x00800000
|
||||||
|
#define VALUE_LIMITER2_THRESHOLD SIGMASTUDIOTYPE_FIXPOINT_CONVERT(1)
|
||||||
|
#define TYPE_LIMITER2_THRESHOLD SIGMASTUDIOTYPE_FIXPOINT
|
||||||
|
#define ADDR_LIMITER2_RMS 65
|
||||||
|
#define FIXPT_LIMITER2_RMS 0x000007DC
|
||||||
|
#define VALUE_LIMITER2_RMS SIGMASTUDIOTYPE_FIXPOINT_CONVERT(0.000239881380791562)
|
||||||
|
#define TYPE_LIMITER2_RMS SIGMASTUDIOTYPE_FIXPOINT
|
||||||
|
#define ADDR_LIMITER2_DECAY 66
|
||||||
|
#define FIXPT_LIMITER2_DECAY 0x00000800
|
||||||
|
#define VALUE_LIMITER2_DECAY SIGMASTUDIOTYPE_FIXPOINT_CONVERT(0.000244140625)
|
||||||
|
#define TYPE_LIMITER2_DECAY SIGMASTUDIOTYPE_FIXPOINT
|
||||||
|
#define ADDR_LIMITER2_DECAYCOMPLEMENT 67
|
||||||
|
#define FIXPT_LIMITER2_DECAYCOMPLEMENT 0x007FF000
|
||||||
|
#define VALUE_LIMITER2_DECAYCOMPLEMENT SIGMASTUDIOTYPE_FIXPOINT_CONVERT(0.99951171875)
|
||||||
|
#define TYPE_LIMITER2_DECAYCOMPLEMENT SIGMASTUDIOTYPE_FIXPOINT
|
||||||
|
|
||||||
|
#endif
|
||||||
@@ -0,0 +1,565 @@
|
|||||||
|
/*
|
||||||
|
* File: C:\Users\mikbi\Documents\Allegati\Desktop\DigiRadio\DigiRadio_IC_1_REG.h
|
||||||
|
*
|
||||||
|
* Created: Monday, July 6, 2026 2:41:28 PM
|
||||||
|
* Description: DigiRadio:IC 1 control register definitions.
|
||||||
|
*
|
||||||
|
* This software is distributed in the hope that it will be useful,
|
||||||
|
* but is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR
|
||||||
|
* CONDITIONS OF ANY KIND, without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
|
||||||
|
*
|
||||||
|
* This software may only be used to program products purchased from
|
||||||
|
* Analog Devices for incorporation by you into audio products that
|
||||||
|
* are intended for resale to audio product end users. This software
|
||||||
|
* may not be distributed whole or in any part to third parties.
|
||||||
|
*
|
||||||
|
* Copyright ©2026 Analog Devices, Inc. All rights reserved.
|
||||||
|
*/
|
||||||
|
#ifndef __DIGIRADIO_IC_1_REG_H__
|
||||||
|
#define __DIGIRADIO_IC_1_REG_H__
|
||||||
|
|
||||||
|
|
||||||
|
/* InterfaceRegister0 - Registers (IC 1) */
|
||||||
|
#define REG_INTERFACEREGISTER0_IC_1_ADDR 0x800
|
||||||
|
#define REG_INTERFACEREGISTER0_IC_1_BYTE 4
|
||||||
|
#define REG_INTERFACEREGISTER0_IC_1_VALUE 0x0
|
||||||
|
|
||||||
|
/* InterfaceRegister1 - Registers (IC 1) */
|
||||||
|
#define REG_INTERFACEREGISTER1_IC_1_ADDR 0x801
|
||||||
|
#define REG_INTERFACEREGISTER1_IC_1_BYTE 4
|
||||||
|
#define REG_INTERFACEREGISTER1_IC_1_VALUE 0x0
|
||||||
|
|
||||||
|
/* InterfaceRegister2 - Registers (IC 1) */
|
||||||
|
#define REG_INTERFACEREGISTER2_IC_1_ADDR 0x802
|
||||||
|
#define REG_INTERFACEREGISTER2_IC_1_BYTE 4
|
||||||
|
#define REG_INTERFACEREGISTER2_IC_1_VALUE 0x0
|
||||||
|
|
||||||
|
/* InterfaceRegister3 - Registers (IC 1) */
|
||||||
|
#define REG_INTERFACEREGISTER3_IC_1_ADDR 0x803
|
||||||
|
#define REG_INTERFACEREGISTER3_IC_1_BYTE 4
|
||||||
|
#define REG_INTERFACEREGISTER3_IC_1_VALUE 0x0
|
||||||
|
|
||||||
|
/* InterfaceRegister4 - Registers (IC 1) */
|
||||||
|
#define REG_INTERFACEREGISTER4_IC_1_ADDR 0x804
|
||||||
|
#define REG_INTERFACEREGISTER4_IC_1_BYTE 4
|
||||||
|
#define REG_INTERFACEREGISTER4_IC_1_VALUE 0x0
|
||||||
|
|
||||||
|
/* InterfaceRegister5 - Registers (IC 1) */
|
||||||
|
#define REG_INTERFACEREGISTER5_IC_1_ADDR 0x805
|
||||||
|
#define REG_INTERFACEREGISTER5_IC_1_BYTE 4
|
||||||
|
#define REG_INTERFACEREGISTER5_IC_1_VALUE 0x0
|
||||||
|
|
||||||
|
/* InterfaceRegister6 - Registers (IC 1) */
|
||||||
|
#define REG_INTERFACEREGISTER6_IC_1_ADDR 0x806
|
||||||
|
#define REG_INTERFACEREGISTER6_IC_1_BYTE 4
|
||||||
|
#define REG_INTERFACEREGISTER6_IC_1_VALUE 0x0
|
||||||
|
|
||||||
|
/* InterfaceRegister7 - Registers (IC 1) */
|
||||||
|
#define REG_INTERFACEREGISTER7_IC_1_ADDR 0x807
|
||||||
|
#define REG_INTERFACEREGISTER7_IC_1_BYTE 4
|
||||||
|
#define REG_INTERFACEREGISTER7_IC_1_VALUE 0x0
|
||||||
|
|
||||||
|
/* GpioAllRegister - Registers (IC 1) */
|
||||||
|
#define REG_GPIOALLREGISTER_IC_1_ADDR 0x808
|
||||||
|
#define REG_GPIOALLREGISTER_IC_1_BYTE 2
|
||||||
|
#define REG_GPIOALLREGISTER_IC_1_VALUE 0x0
|
||||||
|
|
||||||
|
/* Adc0 - Registers (IC 1) */
|
||||||
|
#define REG_ADC0_IC_1_ADDR 0x809
|
||||||
|
#define REG_ADC0_IC_1_BYTE 1
|
||||||
|
#define REG_ADC0_IC_1_VALUE 0x0
|
||||||
|
|
||||||
|
/* Adc1 - Registers (IC 1) */
|
||||||
|
#define REG_ADC1_IC_1_ADDR 0x80A
|
||||||
|
#define REG_ADC1_IC_1_BYTE 1
|
||||||
|
#define REG_ADC1_IC_1_VALUE 0x0
|
||||||
|
|
||||||
|
/* Adc2 - Registers (IC 1) */
|
||||||
|
#define REG_ADC2_IC_1_ADDR 0x80B
|
||||||
|
#define REG_ADC2_IC_1_BYTE 1
|
||||||
|
#define REG_ADC2_IC_1_VALUE 0x0
|
||||||
|
|
||||||
|
/* Adc3 - Registers (IC 1) */
|
||||||
|
#define REG_ADC3_IC_1_ADDR 0x80C
|
||||||
|
#define REG_ADC3_IC_1_BYTE 1
|
||||||
|
#define REG_ADC3_IC_1_VALUE 0x0
|
||||||
|
|
||||||
|
/* CoreRegister - Registers (IC 1) */
|
||||||
|
#define REG_COREREGISTER_IC_1_ADDR 0x81C
|
||||||
|
#define REG_COREREGISTER_IC_1_BYTE 2
|
||||||
|
#define REG_COREREGISTER_IC_1_VALUE 0x1C
|
||||||
|
|
||||||
|
/* RAMRegister - Registers (IC 1) */
|
||||||
|
#define REG_RAMREGISTER_IC_1_ADDR 0x81D
|
||||||
|
#define REG_RAMREGISTER_IC_1_BYTE 1
|
||||||
|
#define REG_RAMREGISTER_IC_1_VALUE 0x8
|
||||||
|
|
||||||
|
/* SerialOutRegister1 - Registers (IC 1) */
|
||||||
|
#define REG_SERIALOUTREGISTER1_IC_1_ADDR 0x81E
|
||||||
|
#define REG_SERIALOUTREGISTER1_IC_1_BYTE 2
|
||||||
|
#define REG_SERIALOUTREGISTER1_IC_1_VALUE 0x800
|
||||||
|
|
||||||
|
/* SerialInputRegister - Registers (IC 1) */
|
||||||
|
#define REG_SERIALINPUTREGISTER_IC_1_ADDR 0x81F
|
||||||
|
#define REG_SERIALINPUTREGISTER_IC_1_BYTE 1
|
||||||
|
#define REG_SERIALINPUTREGISTER_IC_1_VALUE 0x0
|
||||||
|
|
||||||
|
/* MpCfg0 - Registers (IC 1) */
|
||||||
|
#define REG_MPCFG0_IC_1_ADDR 0x820
|
||||||
|
#define REG_MPCFG0_IC_1_BYTE 3
|
||||||
|
#define REG_MPCFG0_IC_1_VALUE 0x440044
|
||||||
|
|
||||||
|
/* MpCfg1 - Registers (IC 1) */
|
||||||
|
#define REG_MPCFG1_IC_1_ADDR 0x821
|
||||||
|
#define REG_MPCFG1_IC_1_BYTE 3
|
||||||
|
#define REG_MPCFG1_IC_1_VALUE 0x440004
|
||||||
|
|
||||||
|
/* AnalogPowerDownRegister - Registers (IC 1) */
|
||||||
|
#define REG_ANALOGPOWERDOWNREGISTER_IC_1_ADDR 0x822
|
||||||
|
#define REG_ANALOGPOWERDOWNREGISTER_IC_1_BYTE 2
|
||||||
|
#define REG_ANALOGPOWERDOWNREGISTER_IC_1_VALUE 0x0
|
||||||
|
|
||||||
|
/* TestRegister - Registers (IC 1) */
|
||||||
|
#define REG_TESTREGISTER_IC_1_ADDR 0x823
|
||||||
|
#define REG_TESTREGISTER_IC_1_BYTE 2
|
||||||
|
#define REG_TESTREGISTER_IC_1_VALUE 0x0
|
||||||
|
|
||||||
|
/* AnalogInterfaceRegister0 - Registers (IC 1) */
|
||||||
|
#define REG_ANALOGINTERFACEREGISTER0_IC_1_ADDR 0x824
|
||||||
|
#define REG_ANALOGINTERFACEREGISTER0_IC_1_BYTE 2
|
||||||
|
#define REG_ANALOGINTERFACEREGISTER0_IC_1_VALUE 0x8000
|
||||||
|
|
||||||
|
/* AnalogInterfaceRegister1 - Registers (IC 1) */
|
||||||
|
#define REG_ANALOGINTERFACEREGISTER1_IC_1_ADDR 0x825
|
||||||
|
#define REG_ANALOGINTERFACEREGISTER1_IC_1_BYTE 2
|
||||||
|
#define REG_ANALOGINTERFACEREGISTER1_IC_1_VALUE 0x0
|
||||||
|
|
||||||
|
/* AnalogInterfaceRegister2 - Registers (IC 1) */
|
||||||
|
#define REG_ANALOGINTERFACEREGISTER2_IC_1_ADDR 0x826
|
||||||
|
#define REG_ANALOGINTERFACEREGISTER2_IC_1_BYTE 2
|
||||||
|
#define REG_ANALOGINTERFACEREGISTER2_IC_1_VALUE 0x0
|
||||||
|
|
||||||
|
/* AnalogInterfaceRegister3 - Registers (IC 1) */
|
||||||
|
#define REG_ANALOGINTERFACEREGISTER3_IC_1_ADDR 0x827
|
||||||
|
#define REG_ANALOGINTERFACEREGISTER3_IC_1_BYTE 2
|
||||||
|
#define REG_ANALOGINTERFACEREGISTER3_IC_1_VALUE 0x1
|
||||||
|
|
||||||
|
|
||||||
|
/*
|
||||||
|
*
|
||||||
|
* Control register's field descriptions
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
|
||||||
|
/* InterfaceRegister0 (IC 1) */
|
||||||
|
#define R0_INTERFACEREGISTER0_IC_1 0x00000000 /* 00000000000000000000000000000000b [31:0] */
|
||||||
|
#define R0_INTERFACEREGISTER0_IC_1_MASK 0xFFFFFFFF
|
||||||
|
#define R0_INTERFACEREGISTER0_IC_1_SHIFT 0
|
||||||
|
|
||||||
|
/* InterfaceRegister1 (IC 1) */
|
||||||
|
#define R1_INTERFACEREGISTER1_IC_1 0x00000000 /* 00000000000000000000000000000000b [31:0] */
|
||||||
|
#define R1_INTERFACEREGISTER1_IC_1_MASK 0xFFFFFFFF
|
||||||
|
#define R1_INTERFACEREGISTER1_IC_1_SHIFT 0
|
||||||
|
|
||||||
|
/* InterfaceRegister2 (IC 1) */
|
||||||
|
#define R2_INTERFACEREGISTER2_IC_1 0x00000000 /* 00000000000000000000000000000000b [31:0] */
|
||||||
|
#define R2_INTERFACEREGISTER2_IC_1_MASK 0xFFFFFFFF
|
||||||
|
#define R2_INTERFACEREGISTER2_IC_1_SHIFT 0
|
||||||
|
|
||||||
|
/* InterfaceRegister3 (IC 1) */
|
||||||
|
#define R3_INTERFACEREGISTER3_IC_1 0x00000000 /* 00000000000000000000000000000000b [31:0] */
|
||||||
|
#define R3_INTERFACEREGISTER3_IC_1_MASK 0xFFFFFFFF
|
||||||
|
#define R3_INTERFACEREGISTER3_IC_1_SHIFT 0
|
||||||
|
|
||||||
|
/* InterfaceRegister4 (IC 1) */
|
||||||
|
#define R4_INTERFACEREGISTER4_IC_1 0x00000000 /* 00000000000000000000000000000000b [31:0] */
|
||||||
|
#define R4_INTERFACEREGISTER4_IC_1_MASK 0xFFFFFFFF
|
||||||
|
#define R4_INTERFACEREGISTER4_IC_1_SHIFT 0
|
||||||
|
|
||||||
|
/* InterfaceRegister5 (IC 1) */
|
||||||
|
#define R5_INTERFACEREGISTER5_IC_1 0x00000000 /* 00000000000000000000000000000000b [31:0] */
|
||||||
|
#define R5_INTERFACEREGISTER5_IC_1_MASK 0xFFFFFFFF
|
||||||
|
#define R5_INTERFACEREGISTER5_IC_1_SHIFT 0
|
||||||
|
|
||||||
|
/* InterfaceRegister6 (IC 1) */
|
||||||
|
#define R6_INTERFACEREGISTER6_IC_1 0x00000000 /* 00000000000000000000000000000000b [31:0] */
|
||||||
|
#define R6_INTERFACEREGISTER6_IC_1_MASK 0xFFFFFFFF
|
||||||
|
#define R6_INTERFACEREGISTER6_IC_1_SHIFT 0
|
||||||
|
|
||||||
|
/* InterfaceRegister7 (IC 1) */
|
||||||
|
#define R7_INTERFACEREGISTER7_IC_1 0x00000000 /* 00000000000000000000000000000000b [31:0] */
|
||||||
|
#define R7_INTERFACEREGISTER7_IC_1_MASK 0xFFFFFFFF
|
||||||
|
#define R7_INTERFACEREGISTER7_IC_1_SHIFT 0
|
||||||
|
|
||||||
|
/* GpioAllRegister (IC 1) */
|
||||||
|
#define R8_PIN0_IC_1 0x0 /* 0b [0] */
|
||||||
|
#define R8_PIN1_IC_1 0x0 /* 0b [1] */
|
||||||
|
#define R8_PIN2_IC_1 0x0 /* 0b [2] */
|
||||||
|
#define R8_PIN3_IC_1 0x0 /* 0b [3] */
|
||||||
|
#define R8_PIN4_IC_1 0x0 /* 0b [4] */
|
||||||
|
#define R8_PIN5_IC_1 0x0 /* 0b [5] */
|
||||||
|
#define R8_PIN6_IC_1 0x0 /* 0b [6] */
|
||||||
|
#define R8_PIN7_IC_1 0x0 /* 0b [7] */
|
||||||
|
#define R8_PIN8_IC_1 0x0 /* 0b [8] */
|
||||||
|
#define R8_PIN9_IC_1 0x0 /* 0b [9] */
|
||||||
|
#define R8_PIN10_IC_1 0x0 /* 0b [10] */
|
||||||
|
#define R8_PIN11_IC_1 0x0 /* 0b [11] */
|
||||||
|
#define R8_PIN0_IC_1_MASK 0x1
|
||||||
|
#define R8_PIN0_IC_1_SHIFT 0
|
||||||
|
#define R8_PIN1_IC_1_MASK 0x2
|
||||||
|
#define R8_PIN1_IC_1_SHIFT 1
|
||||||
|
#define R8_PIN2_IC_1_MASK 0x4
|
||||||
|
#define R8_PIN2_IC_1_SHIFT 2
|
||||||
|
#define R8_PIN3_IC_1_MASK 0x8
|
||||||
|
#define R8_PIN3_IC_1_SHIFT 3
|
||||||
|
#define R8_PIN4_IC_1_MASK 0x10
|
||||||
|
#define R8_PIN4_IC_1_SHIFT 4
|
||||||
|
#define R8_PIN5_IC_1_MASK 0x20
|
||||||
|
#define R8_PIN5_IC_1_SHIFT 5
|
||||||
|
#define R8_PIN6_IC_1_MASK 0x40
|
||||||
|
#define R8_PIN6_IC_1_SHIFT 6
|
||||||
|
#define R8_PIN7_IC_1_MASK 0x80
|
||||||
|
#define R8_PIN7_IC_1_SHIFT 7
|
||||||
|
#define R8_PIN8_IC_1_MASK 0x100
|
||||||
|
#define R8_PIN8_IC_1_SHIFT 8
|
||||||
|
#define R8_PIN9_IC_1_MASK 0x200
|
||||||
|
#define R8_PIN9_IC_1_SHIFT 9
|
||||||
|
#define R8_PIN10_IC_1_MASK 0x400
|
||||||
|
#define R8_PIN10_IC_1_SHIFT 10
|
||||||
|
#define R8_PIN11_IC_1_MASK 0x800
|
||||||
|
#define R8_PIN11_IC_1_SHIFT 11
|
||||||
|
|
||||||
|
/* Adc0 (IC 1) */
|
||||||
|
#define R9_ADC0_IC_1 0x00 /* 00000000b [7:0] */
|
||||||
|
#define R9_ADC0_IC_1_MASK 0xFF
|
||||||
|
#define R9_ADC0_IC_1_SHIFT 0
|
||||||
|
|
||||||
|
/* Adc1 (IC 1) */
|
||||||
|
#define R10_ADC1_IC_1 0x00 /* 00000000b [7:0] */
|
||||||
|
#define R10_ADC1_IC_1_MASK 0xFF
|
||||||
|
#define R10_ADC1_IC_1_SHIFT 0
|
||||||
|
|
||||||
|
/* Adc2 (IC 1) */
|
||||||
|
#define R11_ADC2_IC_1 0x00 /* 00000000b [7:0] */
|
||||||
|
#define R11_ADC2_IC_1_MASK 0xFF
|
||||||
|
#define R11_ADC2_IC_1_SHIFT 0
|
||||||
|
|
||||||
|
/* Adc3 (IC 1) */
|
||||||
|
#define R12_ADC3_IC_1 0x00 /* 00000000b [7:0] */
|
||||||
|
#define R12_ADC3_IC_1_MASK 0xFF
|
||||||
|
#define R12_ADC3_IC_1_SHIFT 0
|
||||||
|
|
||||||
|
/* CoreRegister (IC 1) */
|
||||||
|
#define R13_PROGRAM_LENGTH_IC_1 0x0 /* 00b [1:0] */
|
||||||
|
#define R13_REGISTER_ZERO_IC_1 0x1 /* 1b [2] */
|
||||||
|
#define R13_MUTE_DAC_IC_1 0x1 /* 1b [3] */
|
||||||
|
#define R13_MUTE_ADC_IC_1 0x1 /* 1b [4] */
|
||||||
|
#define R13_SAFELOAD_IC_1 0x0 /* 0b [5] */
|
||||||
|
#define R13_WRITESPI_INTERFACEREG_IC_1 0x0 /* 0b [6] */
|
||||||
|
#define R13_WRITESPI_GPIO_IC_1 0x0 /* 0b [7] */
|
||||||
|
#define R13_WRITESPI_ADC_IC_1 0x0 /* 0b [8] */
|
||||||
|
#define R13_MUTE_SERIALIN_IC_1 0x0 /* 0b [9] */
|
||||||
|
#define R13_GPIO_DEBOUNCE_IC_1 0x0 /* 00b [11:10] */
|
||||||
|
#define R13_EXTMEM_SPEED_IC_1 0x0 /* 00b [13:12] */
|
||||||
|
#define R13_PROGRAM_LENGTH_IC_1_MASK 0x3
|
||||||
|
#define R13_PROGRAM_LENGTH_IC_1_SHIFT 0
|
||||||
|
#define R13_REGISTER_ZERO_IC_1_MASK 0x4
|
||||||
|
#define R13_REGISTER_ZERO_IC_1_SHIFT 2
|
||||||
|
#define R13_MUTE_DAC_IC_1_MASK 0x8
|
||||||
|
#define R13_MUTE_DAC_IC_1_SHIFT 3
|
||||||
|
#define R13_MUTE_ADC_IC_1_MASK 0x10
|
||||||
|
#define R13_MUTE_ADC_IC_1_SHIFT 4
|
||||||
|
#define R13_SAFELOAD_IC_1_MASK 0x20
|
||||||
|
#define R13_SAFELOAD_IC_1_SHIFT 5
|
||||||
|
#define R13_WRITESPI_INTERFACEREG_IC_1_MASK 0x40
|
||||||
|
#define R13_WRITESPI_INTERFACEREG_IC_1_SHIFT 6
|
||||||
|
#define R13_WRITESPI_GPIO_IC_1_MASK 0x80
|
||||||
|
#define R13_WRITESPI_GPIO_IC_1_SHIFT 7
|
||||||
|
#define R13_WRITESPI_ADC_IC_1_MASK 0x100
|
||||||
|
#define R13_WRITESPI_ADC_IC_1_SHIFT 8
|
||||||
|
#define R13_MUTE_SERIALIN_IC_1_MASK 0x200
|
||||||
|
#define R13_MUTE_SERIALIN_IC_1_SHIFT 9
|
||||||
|
#define R13_GPIO_DEBOUNCE_IC_1_MASK 0xC00
|
||||||
|
#define R13_GPIO_DEBOUNCE_IC_1_SHIFT 10
|
||||||
|
#define R13_EXTMEM_SPEED_IC_1_MASK 0x3000
|
||||||
|
#define R13_EXTMEM_SPEED_IC_1_SHIFT 12
|
||||||
|
|
||||||
|
/* RAMRegister (IC 1) */
|
||||||
|
#define R14_RAM_MODULO_IC_1 0x8 /* 1000b [3:0] */
|
||||||
|
#define R14_RAM_MODULO_IC_1_MASK 0xF
|
||||||
|
#define R14_RAM_MODULO_IC_1_SHIFT 0
|
||||||
|
|
||||||
|
/* SerialOutRegister1 (IC 1) */
|
||||||
|
#define R15_WORDLENGTH_IC_1 0x0 /* 00b [1:0] */
|
||||||
|
#define R15_MSB_POS_IC_1 0x0 /* 000b [4:2] */
|
||||||
|
#define R15_TDM_IC_1 0x0 /* 0b [5] */
|
||||||
|
#define R15_FRAMESYNC_TYPE_IC_1 0x0 /* 0b [6] */
|
||||||
|
#define R15_LRCLK_FREQ_IC_1 0x0 /* 00b [8:7] */
|
||||||
|
#define R15_BCLK_FREQ_IC_1 0x0 /* 00b [10:9] */
|
||||||
|
#define R15_MASTER_SLAVE_IC_1 0x1 /* 1b [11] */
|
||||||
|
#define R15_BCLK_POLARITY_IC_1 0x0 /* 0b [12] */
|
||||||
|
#define R15_LRCLK_POLARITY_IC_1 0x0 /* 0b [13] */
|
||||||
|
#define R15_WORDLENGTH_IC_1_MASK 0x3
|
||||||
|
#define R15_WORDLENGTH_IC_1_SHIFT 0
|
||||||
|
#define R15_MSB_POS_IC_1_MASK 0x1C
|
||||||
|
#define R15_MSB_POS_IC_1_SHIFT 2
|
||||||
|
#define R15_TDM_IC_1_MASK 0x20
|
||||||
|
#define R15_TDM_IC_1_SHIFT 5
|
||||||
|
#define R15_FRAMESYNC_TYPE_IC_1_MASK 0x40
|
||||||
|
#define R15_FRAMESYNC_TYPE_IC_1_SHIFT 6
|
||||||
|
#define R15_LRCLK_FREQ_IC_1_MASK 0x180
|
||||||
|
#define R15_LRCLK_FREQ_IC_1_SHIFT 7
|
||||||
|
#define R15_BCLK_FREQ_IC_1_MASK 0x600
|
||||||
|
#define R15_BCLK_FREQ_IC_1_SHIFT 9
|
||||||
|
#define R15_MASTER_SLAVE_IC_1_MASK 0x800
|
||||||
|
#define R15_MASTER_SLAVE_IC_1_SHIFT 11
|
||||||
|
#define R15_BCLK_POLARITY_IC_1_MASK 0x1000
|
||||||
|
#define R15_BCLK_POLARITY_IC_1_SHIFT 12
|
||||||
|
#define R15_LRCLK_POLARITY_IC_1_MASK 0x2000
|
||||||
|
#define R15_LRCLK_POLARITY_IC_1_SHIFT 13
|
||||||
|
|
||||||
|
/* SerialInputRegister (IC 1) */
|
||||||
|
#define R16_INPUT_MODE_IC_1 0x0 /* 000b [2:0] */
|
||||||
|
#define R16_BCLK_POLARITY_IC_1 0x0 /* 0b [3] */
|
||||||
|
#define R16_LRCLK_POLARITY_IC_1 0x0 /* 0b [4] */
|
||||||
|
#define R16_INPUT_MODE_IC_1_MASK 0x7
|
||||||
|
#define R16_INPUT_MODE_IC_1_SHIFT 0
|
||||||
|
#define R16_BCLK_POLARITY_IC_1_MASK 0x8
|
||||||
|
#define R16_BCLK_POLARITY_IC_1_SHIFT 3
|
||||||
|
#define R16_LRCLK_POLARITY_IC_1_MASK 0x10
|
||||||
|
#define R16_LRCLK_POLARITY_IC_1_SHIFT 4
|
||||||
|
|
||||||
|
/* MpCfg0 (IC 1) */
|
||||||
|
#define R17_MFSELECT0_IC_1 0x4 /* 100b [2:0] */
|
||||||
|
#define R17_MFINVERT0_IC_1 0x0 /* 0b [3] */
|
||||||
|
#define R17_MFSELECT1_IC_1 0x4 /* 100b [6:4] */
|
||||||
|
#define R17_MFINVERT1_IC_1 0x0 /* 0b [7] */
|
||||||
|
#define R17_MFSELECT2_IC_1 0x0 /* 000b [10:8] */
|
||||||
|
#define R17_MFINVERT2_IC_1 0x0 /* 0b [11] */
|
||||||
|
#define R17_MFSELECT3_IC_1 0x0 /* 000b [14:12] */
|
||||||
|
#define R17_MFINVERT3_IC_1 0x0 /* 0b [15] */
|
||||||
|
#define R17_MFSELECT4_IC_1 0x4 /* 100b [18:16] */
|
||||||
|
#define R17_MFINVERT4_IC_1 0x0 /* 0b [19] */
|
||||||
|
#define R17_MFSELECT5_IC_1 0x4 /* 100b [22:20] */
|
||||||
|
#define R17_MFINVERT5_IC_1 0x0 /* 0b [23] */
|
||||||
|
#define R17_MFSELECT0_IC_1_MASK 0x7
|
||||||
|
#define R17_MFSELECT0_IC_1_SHIFT 0
|
||||||
|
#define R17_MFINVERT0_IC_1_MASK 0x8
|
||||||
|
#define R17_MFINVERT0_IC_1_SHIFT 3
|
||||||
|
#define R17_MFSELECT1_IC_1_MASK 0x70
|
||||||
|
#define R17_MFSELECT1_IC_1_SHIFT 4
|
||||||
|
#define R17_MFINVERT1_IC_1_MASK 0x80
|
||||||
|
#define R17_MFINVERT1_IC_1_SHIFT 7
|
||||||
|
#define R17_MFSELECT2_IC_1_MASK 0x700
|
||||||
|
#define R17_MFSELECT2_IC_1_SHIFT 8
|
||||||
|
#define R17_MFINVERT2_IC_1_MASK 0x800
|
||||||
|
#define R17_MFINVERT2_IC_1_SHIFT 11
|
||||||
|
#define R17_MFSELECT3_IC_1_MASK 0x7000
|
||||||
|
#define R17_MFSELECT3_IC_1_SHIFT 12
|
||||||
|
#define R17_MFINVERT3_IC_1_MASK 0x8000
|
||||||
|
#define R17_MFINVERT3_IC_1_SHIFT 15
|
||||||
|
#define R17_MFSELECT4_IC_1_MASK 0x70000
|
||||||
|
#define R17_MFSELECT4_IC_1_SHIFT 16
|
||||||
|
#define R17_MFINVERT4_IC_1_MASK 0x80000
|
||||||
|
#define R17_MFINVERT4_IC_1_SHIFT 19
|
||||||
|
#define R17_MFSELECT5_IC_1_MASK 0x700000
|
||||||
|
#define R17_MFSELECT5_IC_1_SHIFT 20
|
||||||
|
#define R17_MFINVERT5_IC_1_MASK 0x800000
|
||||||
|
#define R17_MFINVERT5_IC_1_SHIFT 23
|
||||||
|
|
||||||
|
/* MpCfg1 (IC 1) */
|
||||||
|
#define R18_MFSELECT6_IC_1 0x4 /* 100b [2:0] */
|
||||||
|
#define R18_MFINVERT6_IC_1 0x0 /* 0b [3] */
|
||||||
|
#define R18_MFSELECT7_IC_1 0x0 /* 000b [6:4] */
|
||||||
|
#define R18_MFINVERT7_IC_1 0x0 /* 0b [7] */
|
||||||
|
#define R18_MFSELECT8_IC_1 0x0 /* 000b [10:8] */
|
||||||
|
#define R18_MFINVERT8_IC_1 0x0 /* 0b [11] */
|
||||||
|
#define R18_MFSELECT9_IC_1 0x0 /* 000b [14:12] */
|
||||||
|
#define R18_MFINVERT9_IC_1 0x0 /* 0b [15] */
|
||||||
|
#define R18_MFSELECT10_IC_1 0x4 /* 100b [18:16] */
|
||||||
|
#define R18_MFINVERT10_IC_1 0x0 /* 0b [19] */
|
||||||
|
#define R18_MFSELECT11_IC_1 0x4 /* 100b [22:20] */
|
||||||
|
#define R18_MFINVERT11_IC_1 0x0 /* 0b [23] */
|
||||||
|
#define R18_MFSELECT6_IC_1_MASK 0x7
|
||||||
|
#define R18_MFSELECT6_IC_1_SHIFT 0
|
||||||
|
#define R18_MFINVERT6_IC_1_MASK 0x8
|
||||||
|
#define R18_MFINVERT6_IC_1_SHIFT 3
|
||||||
|
#define R18_MFSELECT7_IC_1_MASK 0x70
|
||||||
|
#define R18_MFSELECT7_IC_1_SHIFT 4
|
||||||
|
#define R18_MFINVERT7_IC_1_MASK 0x80
|
||||||
|
#define R18_MFINVERT7_IC_1_SHIFT 7
|
||||||
|
#define R18_MFSELECT8_IC_1_MASK 0x700
|
||||||
|
#define R18_MFSELECT8_IC_1_SHIFT 8
|
||||||
|
#define R18_MFINVERT8_IC_1_MASK 0x800
|
||||||
|
#define R18_MFINVERT8_IC_1_SHIFT 11
|
||||||
|
#define R18_MFSELECT9_IC_1_MASK 0x7000
|
||||||
|
#define R18_MFSELECT9_IC_1_SHIFT 12
|
||||||
|
#define R18_MFINVERT9_IC_1_MASK 0x8000
|
||||||
|
#define R18_MFINVERT9_IC_1_SHIFT 15
|
||||||
|
#define R18_MFSELECT10_IC_1_MASK 0x70000
|
||||||
|
#define R18_MFSELECT10_IC_1_SHIFT 16
|
||||||
|
#define R18_MFINVERT10_IC_1_MASK 0x80000
|
||||||
|
#define R18_MFINVERT10_IC_1_SHIFT 19
|
||||||
|
#define R18_MFSELECT11_IC_1_MASK 0x700000
|
||||||
|
#define R18_MFSELECT11_IC_1_SHIFT 20
|
||||||
|
#define R18_MFINVERT11_IC_1_MASK 0x800000
|
||||||
|
#define R18_MFINVERT11_IC_1_SHIFT 23
|
||||||
|
|
||||||
|
/* AnalogPowerDownRegister (IC 1) */
|
||||||
|
#define R19_DAC3_POWERDOWN_IC_1 0x0 /* 0b [0] */
|
||||||
|
#define R19_DAC2_POWERDOWN_IC_1 0x0 /* 0b [1] */
|
||||||
|
#define R19_DAC1_POWERDOWN_IC_1 0x0 /* 0b [2] */
|
||||||
|
#define R19_DAC0_POWERDOWN_IC_1 0x0 /* 0b [3] */
|
||||||
|
#define R19_DACS_RESET_IC_1 0x0 /* 0b [4] */
|
||||||
|
#define R19_REF_CORE_POWERDOWN_IC_1 0x0 /* 0b [5] */
|
||||||
|
#define R19_REF_BUF_POWERDOWN_IC_1 0x0 /* 0b [6] */
|
||||||
|
#define R19_ADCS_POWERDOWN_IC_1 0x0 /* 0b [7] */
|
||||||
|
#define R19_AUX_ADC_FILTER_IC_1 0x0 /* 000b [10:8] */
|
||||||
|
#define R19_DAC3_POWERDOWN_IC_1_MASK 0x1
|
||||||
|
#define R19_DAC3_POWERDOWN_IC_1_SHIFT 0
|
||||||
|
#define R19_DAC2_POWERDOWN_IC_1_MASK 0x2
|
||||||
|
#define R19_DAC2_POWERDOWN_IC_1_SHIFT 1
|
||||||
|
#define R19_DAC1_POWERDOWN_IC_1_MASK 0x4
|
||||||
|
#define R19_DAC1_POWERDOWN_IC_1_SHIFT 2
|
||||||
|
#define R19_DAC0_POWERDOWN_IC_1_MASK 0x8
|
||||||
|
#define R19_DAC0_POWERDOWN_IC_1_SHIFT 3
|
||||||
|
#define R19_DACS_RESET_IC_1_MASK 0x10
|
||||||
|
#define R19_DACS_RESET_IC_1_SHIFT 4
|
||||||
|
#define R19_REF_CORE_POWERDOWN_IC_1_MASK 0x20
|
||||||
|
#define R19_REF_CORE_POWERDOWN_IC_1_SHIFT 5
|
||||||
|
#define R19_REF_BUF_POWERDOWN_IC_1_MASK 0x40
|
||||||
|
#define R19_REF_BUF_POWERDOWN_IC_1_SHIFT 6
|
||||||
|
#define R19_ADCS_POWERDOWN_IC_1_MASK 0x80
|
||||||
|
#define R19_ADCS_POWERDOWN_IC_1_SHIFT 7
|
||||||
|
#define R19_AUX_ADC_FILTER_IC_1_MASK 0x700
|
||||||
|
#define R19_AUX_ADC_FILTER_IC_1_SHIFT 8
|
||||||
|
|
||||||
|
/* TestRegister (IC 1) */
|
||||||
|
#define R20_ADCCLOCKDELAY_IC_1 0x0 /* 000b [2:0] */
|
||||||
|
#define R20_ADCSYNCFILTL_SDATAA_D_IC_1 0x0 /* 0b [3] */
|
||||||
|
#define R20_ADCSYNCFILTR_SDATAA_D_IC_1 0x0 /* 0b [4] */
|
||||||
|
#define R20_SPIKEFILT_SEROUT0_1_IC_1 0x0 /* 0b [5] */
|
||||||
|
#define R20_UNUSED_IC_1 0x00 /* 0000000000b [15:6] */
|
||||||
|
#define R20_ADCCLOCKDELAY_IC_1_MASK 0x7
|
||||||
|
#define R20_ADCCLOCKDELAY_IC_1_SHIFT 0
|
||||||
|
#define R20_ADCSYNCFILTL_SDATAA_D_IC_1_MASK 0x8
|
||||||
|
#define R20_ADCSYNCFILTL_SDATAA_D_IC_1_SHIFT 3
|
||||||
|
#define R20_ADCSYNCFILTR_SDATAA_D_IC_1_MASK 0x10
|
||||||
|
#define R20_ADCSYNCFILTR_SDATAA_D_IC_1_SHIFT 4
|
||||||
|
#define R20_SPIKEFILT_SEROUT0_1_IC_1_MASK 0x20
|
||||||
|
#define R20_SPIKEFILT_SEROUT0_1_IC_1_SHIFT 5
|
||||||
|
#define R20_UNUSED_IC_1_MASK 0xFFC0
|
||||||
|
#define R20_UNUSED_IC_1_SHIFT 6
|
||||||
|
|
||||||
|
/* AnalogInterfaceRegister0 (IC 1) */
|
||||||
|
#define R21_CHOP_ENB_INT_AMP_IC_1 0x0 /* 0b [0] */
|
||||||
|
#define R21_PD_INT_AMP_IC_1 0x0 /* 0b [1] */
|
||||||
|
#define R21_ADC_NON_OVERLAP_ADJ_IC_1 0x0 /* 0b [2] */
|
||||||
|
#define R21_ADC_I_SEL_IC_1 0x0 /* 00000b [7:3] */
|
||||||
|
#define R21_ADC_CLK_ADJ_IC_1 0x0 /* 000b [10:8] */
|
||||||
|
#define R21_LCHOP_PMOS_CTRL_IC_1 0x0 /* 00b [12:11] */
|
||||||
|
#define R21_LCHOP_AMP_ENB_IC_1 0x0 /* 0b [13] */
|
||||||
|
#define R21_LSCRAM_ENB_IC_1 0x0 /* 0b [14] */
|
||||||
|
#define R21_AUX_ADC_EN_IC_1 0x1 /* 1b [15] */
|
||||||
|
#define R21_CHOP_ENB_INT_AMP_IC_1_MASK 0x1
|
||||||
|
#define R21_CHOP_ENB_INT_AMP_IC_1_SHIFT 0
|
||||||
|
#define R21_PD_INT_AMP_IC_1_MASK 0x2
|
||||||
|
#define R21_PD_INT_AMP_IC_1_SHIFT 1
|
||||||
|
#define R21_ADC_NON_OVERLAP_ADJ_IC_1_MASK 0x4
|
||||||
|
#define R21_ADC_NON_OVERLAP_ADJ_IC_1_SHIFT 2
|
||||||
|
#define R21_ADC_I_SEL_IC_1_MASK 0xF8
|
||||||
|
#define R21_ADC_I_SEL_IC_1_SHIFT 3
|
||||||
|
#define R21_ADC_CLK_ADJ_IC_1_MASK 0x700
|
||||||
|
#define R21_ADC_CLK_ADJ_IC_1_SHIFT 8
|
||||||
|
#define R21_LCHOP_PMOS_CTRL_IC_1_MASK 0x1800
|
||||||
|
#define R21_LCHOP_PMOS_CTRL_IC_1_SHIFT 11
|
||||||
|
#define R21_LCHOP_AMP_ENB_IC_1_MASK 0x2000
|
||||||
|
#define R21_LCHOP_AMP_ENB_IC_1_SHIFT 13
|
||||||
|
#define R21_LSCRAM_ENB_IC_1_MASK 0x4000
|
||||||
|
#define R21_LSCRAM_ENB_IC_1_SHIFT 14
|
||||||
|
#define R21_AUX_ADC_EN_IC_1_MASK 0x8000
|
||||||
|
#define R21_AUX_ADC_EN_IC_1_SHIFT 15
|
||||||
|
|
||||||
|
/* AnalogInterfaceRegister1 (IC 1) */
|
||||||
|
#define R22_RSWC_DAC_CTRL_IC_1 0x0 /* 00b [1:0] */
|
||||||
|
#define R22_RCLK_SEL_PMOS_IC_1 0x0 /* 00b [3:2] */
|
||||||
|
#define R22_RCLK_SEL_AMP_IC_1 0x0 /* 00b [5:4] */
|
||||||
|
#define R22_RCHOP_PMOS_CTRL_IC_1 0x0 /* 00b [7:6] */
|
||||||
|
#define R22_LCLK_SEL_PMOS_IC_1 0x0 /* 00b [9:8] */
|
||||||
|
#define R22_LSWC_DAC_CTRL_IC_1 0x0 /* 00b [11:10] */
|
||||||
|
#define R22_CLK_SEL_INT_AMP_IC_1 0x0 /* 00b [13:12] */
|
||||||
|
#define R22_RSWC_DAC_CTRL_IC_1_MASK 0x3
|
||||||
|
#define R22_RSWC_DAC_CTRL_IC_1_SHIFT 0
|
||||||
|
#define R22_RCLK_SEL_PMOS_IC_1_MASK 0xC
|
||||||
|
#define R22_RCLK_SEL_PMOS_IC_1_SHIFT 2
|
||||||
|
#define R22_RCLK_SEL_AMP_IC_1_MASK 0x30
|
||||||
|
#define R22_RCLK_SEL_AMP_IC_1_SHIFT 4
|
||||||
|
#define R22_RCHOP_PMOS_CTRL_IC_1_MASK 0xC0
|
||||||
|
#define R22_RCHOP_PMOS_CTRL_IC_1_SHIFT 6
|
||||||
|
#define R22_LCLK_SEL_PMOS_IC_1_MASK 0x300
|
||||||
|
#define R22_LCLK_SEL_PMOS_IC_1_SHIFT 8
|
||||||
|
#define R22_LSWC_DAC_CTRL_IC_1_MASK 0xC00
|
||||||
|
#define R22_LSWC_DAC_CTRL_IC_1_SHIFT 10
|
||||||
|
#define R22_CLK_SEL_INT_AMP_IC_1_MASK 0x3000
|
||||||
|
#define R22_CLK_SEL_INT_AMP_IC_1_SHIFT 12
|
||||||
|
|
||||||
|
/* AnalogInterfaceRegister2 (IC 1) */
|
||||||
|
#define R23_UNUSED_IC_1 0x0 /* 0b [0] */
|
||||||
|
#define R23_ADC_DITHER_EN_IC_1 0x0 /* 0b [1] */
|
||||||
|
#define R23_XTAL_ENB_IC_1 0x0 /* 0b [2] */
|
||||||
|
#define R23_PLL_PD_IC_1 0x0 /* 0b [3] */
|
||||||
|
#define R23_IBIAS_TO_PIN_IC_1 0x0 /* 0b [4] */
|
||||||
|
#define R23_IBIAS_ADJ_IC_1 0x0 /* 0000b [8:5] */
|
||||||
|
#define R23_VREF_TRIM_IC_1 0x0 /* 0000b [12:9] */
|
||||||
|
#define R23_REF_I_SEL_IC_1 0x0 /* 0b [13] */
|
||||||
|
#define R23_RSCRAM_ENB_IC_1 0x0 /* 0b [14] */
|
||||||
|
#define R23_LCHOP_AMP_ENB_IC_1 0x0 /* 0b [15] */
|
||||||
|
#define R23_UNUSED_IC_1_MASK 0x1
|
||||||
|
#define R23_UNUSED_IC_1_SHIFT 0
|
||||||
|
#define R23_ADC_DITHER_EN_IC_1_MASK 0x2
|
||||||
|
#define R23_ADC_DITHER_EN_IC_1_SHIFT 1
|
||||||
|
#define R23_XTAL_ENB_IC_1_MASK 0x4
|
||||||
|
#define R23_XTAL_ENB_IC_1_SHIFT 2
|
||||||
|
#define R23_PLL_PD_IC_1_MASK 0x8
|
||||||
|
#define R23_PLL_PD_IC_1_SHIFT 3
|
||||||
|
#define R23_IBIAS_TO_PIN_IC_1_MASK 0x10
|
||||||
|
#define R23_IBIAS_TO_PIN_IC_1_SHIFT 4
|
||||||
|
#define R23_IBIAS_ADJ_IC_1_MASK 0x1E0
|
||||||
|
#define R23_IBIAS_ADJ_IC_1_SHIFT 5
|
||||||
|
#define R23_VREF_TRIM_IC_1_MASK 0x1E00
|
||||||
|
#define R23_VREF_TRIM_IC_1_SHIFT 9
|
||||||
|
#define R23_REF_I_SEL_IC_1_MASK 0x2000
|
||||||
|
#define R23_REF_I_SEL_IC_1_SHIFT 13
|
||||||
|
#define R23_RSCRAM_ENB_IC_1_MASK 0x4000
|
||||||
|
#define R23_RSCRAM_ENB_IC_1_SHIFT 14
|
||||||
|
#define R23_LCHOP_AMP_ENB_IC_1_MASK 0x8000
|
||||||
|
#define R23_LCHOP_AMP_ENB_IC_1_SHIFT 15
|
||||||
|
|
||||||
|
/* AnalogInterfaceRegister3 (IC 1) */
|
||||||
|
#define R24_DAC_OFFSET_IC_1 0x1 /* 01b [1:0] */
|
||||||
|
#define R24_DAC_DITHER_ENABLE_IC_1 0x0 /* 0b [2] */
|
||||||
|
#define R24_PCTRL_IC_1 0x0 /* 00b [4:3] */
|
||||||
|
#define R24_DAC_PCLK_SEL_IC_1 0x0 /* 00b [6:5] */
|
||||||
|
#define R24_DAC_I_SEL_IC_1 0x0 /* 0000b [10:7] */
|
||||||
|
#define R24_DAC_CLK_SEL_IC_1 0x0 /* 00b [12:11] */
|
||||||
|
#define R24_DAC_CHOP_ENB_IC_1 0x0 /* 0b [13] */
|
||||||
|
#define R24_DAC_DRTZ_IC_1 0x0 /* 00b [15:14] */
|
||||||
|
#define R24_DAC_OFFSET_IC_1_MASK 0x3
|
||||||
|
#define R24_DAC_OFFSET_IC_1_SHIFT 0
|
||||||
|
#define R24_DAC_DITHER_ENABLE_IC_1_MASK 0x4
|
||||||
|
#define R24_DAC_DITHER_ENABLE_IC_1_SHIFT 2
|
||||||
|
#define R24_PCTRL_IC_1_MASK 0x18
|
||||||
|
#define R24_PCTRL_IC_1_SHIFT 3
|
||||||
|
#define R24_DAC_PCLK_SEL_IC_1_MASK 0x60
|
||||||
|
#define R24_DAC_PCLK_SEL_IC_1_SHIFT 5
|
||||||
|
#define R24_DAC_I_SEL_IC_1_MASK 0x780
|
||||||
|
#define R24_DAC_I_SEL_IC_1_SHIFT 7
|
||||||
|
#define R24_DAC_CLK_SEL_IC_1_MASK 0x1800
|
||||||
|
#define R24_DAC_CLK_SEL_IC_1_SHIFT 11
|
||||||
|
#define R24_DAC_CHOP_ENB_IC_1_MASK 0x2000
|
||||||
|
#define R24_DAC_CHOP_ENB_IC_1_SHIFT 13
|
||||||
|
#define R24_DAC_DRTZ_IC_1_MASK 0xC000
|
||||||
|
#define R24_DAC_DRTZ_IC_1_SHIFT 14
|
||||||
|
|
||||||
|
#endif
|
||||||
@@ -0,0 +1,80 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<!-- *
|
||||||
|
* This software is distributed in the hope that it will be useful,
|
||||||
|
* but is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR
|
||||||
|
* CONDITIONS OF ANY KIND, without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
|
||||||
|
*
|
||||||
|
* This software may only be used to program products purchased from
|
||||||
|
* Analog Devices for incorporation by you into audio products that
|
||||||
|
* are intended for resale to audio product end users. This software
|
||||||
|
* may not be distributed whole or in any part to third parties.
|
||||||
|
*
|
||||||
|
* Copyright © 2026 Analog Devices, Inc. All rights reserved.
|
||||||
|
*/
|
||||||
|
-->
|
||||||
|
<!--NetList XML file-->
|
||||||
|
<NetList>
|
||||||
|
<IC name=" IC 1 " type="DSPSigma100">
|
||||||
|
<Schematic>
|
||||||
|
<Algorithm name="ICSigma100In1" friendlyname="170x\140x input " cell="Input1 " location="{X=-15, Y=104} " Growth="10 " GrowthB="0 " FS="48000">
|
||||||
|
<Link pin="O_C33_A0_P3_out" dir="out" link="Link0" />
|
||||||
|
<Link pin="O_C33_A0_P4_out" dir="out" link="Link1" />
|
||||||
|
<Link pin="O_C33_A0_P5_out" dir="out" link="Link2" />
|
||||||
|
<Link pin="O_C33_A0_P6_out" dir="out" link="Link3" />
|
||||||
|
</Algorithm>
|
||||||
|
<Algorithm name="Gain1940AlgNS1" friendlyname="Gain (no slew) " cell="Si4674 " location="{X=168, Y=5} " Growth="0 " GrowthB="0">
|
||||||
|
<Link pin="I_C63_A0_P1_in" dir="in" link="Link0" />
|
||||||
|
<Link pin="O_C63_A0_P2_out" dir="out" link="Link4" />
|
||||||
|
</Algorithm>
|
||||||
|
<Algorithm name="Gain1940AlgNS2" friendlyname="Gain (no slew) " cell="Si4674 " location="{X=168, Y=5} " Growth="0 " GrowthB="0">
|
||||||
|
<Link pin="I_C63_A1_P1_in" dir="in" link="Link1" />
|
||||||
|
<Link pin="O_C63_A1_P2_out" dir="out" link="Link5" />
|
||||||
|
</Algorithm>
|
||||||
|
<Algorithm name="Gain1940AlgNS3" friendlyname="Gain (no slew) " cell="ESP32 " location="{X=166, Y=196} " Growth="0 " GrowthB="0">
|
||||||
|
<Link pin="I_C75_A0_P1_in" dir="in" link="Link2" />
|
||||||
|
<Link pin="O_C75_A0_P2_out" dir="out" link="Link6" />
|
||||||
|
</Algorithm>
|
||||||
|
<Algorithm name="Gain1940AlgNS4" friendlyname="Gain (no slew) " cell="ESP32 " location="{X=166, Y=196} " Growth="0 " GrowthB="0">
|
||||||
|
<Link pin="I_C75_A1_P1_in" dir="in" link="Link3" />
|
||||||
|
<Link pin="O_C75_A1_P2_out" dir="out" link="Link7" />
|
||||||
|
</Algorithm>
|
||||||
|
<Algorithm name="StereoMixerAlg1" friendlyname="Stereo Mixer " cell="St Mixer1 " location="{X=381, Y=115} " Growth="2 " GrowthB="0">
|
||||||
|
<Link pin="I_C84_A0_P3_in" dir="in" link="Link4" />
|
||||||
|
<Link pin="I_C84_A0_P4_in" dir="in" link="Link5" />
|
||||||
|
<Link pin="I_C84_A0_P5_in" dir="in" link="Link6" />
|
||||||
|
<Link pin="I_C84_A0_P6_in" dir="in" link="Link7" />
|
||||||
|
<Link pin="O_C84_A0_P1_out" dir="out" link="Link8" />
|
||||||
|
<Link pin="O_C84_A0_P2_out" dir="out" link="Link9" />
|
||||||
|
</Algorithm>
|
||||||
|
<Algorithm name="PEQ1Chan1" friendlyname="PEQ1Chan - Double Precision " cell="Param EQ1 " location="{X=502, Y=156} " Growth="2 " GrowthB="0">
|
||||||
|
<Link pin="I_C125_A0_P1_in" dir="in" link="Link8" />
|
||||||
|
<Link pin="I_C125_A0_P3_in" dir="in" link="Link9" />
|
||||||
|
<Link pin="O_C125_A0_P2_out" dir="out" link="Link10" />
|
||||||
|
<Link pin="O_C125_A0_P4_out" dir="out" link="Link11" />
|
||||||
|
</Algorithm>
|
||||||
|
<Algorithm name="Gain1940AlgNS5" friendlyname="Gain (no slew) " cell="Multiple 1 " location="{X=628, Y=88} " Growth="0 " GrowthB="0">
|
||||||
|
<Link pin="I_C138_A0_P1_in" dir="in" link="Link10" />
|
||||||
|
<Link pin="O_C138_A0_P2_out" dir="out" link="Link12" />
|
||||||
|
</Algorithm>
|
||||||
|
<Algorithm name="Gain1940AlgNS6" friendlyname="Gain (no slew) " cell="Multiple 1 " location="{X=628, Y=88} " Growth="0 " GrowthB="0">
|
||||||
|
<Link pin="I_C138_A1_P1_in" dir="in" link="Link11" />
|
||||||
|
<Link pin="O_C138_A1_P2_out" dir="out" link="Link13" />
|
||||||
|
</Algorithm>
|
||||||
|
<Algorithm name="limiterAlg1" friendlyname="Limiter with Indicator " cell="Limiter 1 " location="{X=761, Y=40} " Growth="0 " GrowthB="0">
|
||||||
|
<Link pin="I_C169_A0_P1_in" dir="in" link="Link12" />
|
||||||
|
<Link pin="O_C169_A0_P2_out" dir="out" link="Link15" />
|
||||||
|
</Algorithm>
|
||||||
|
<Algorithm name="limiterAlg2" friendlyname="Limiter with Indicator " cell="Limiter 2 " location="{X=765, Y=189} " Growth="0 " GrowthB="0">
|
||||||
|
<Link pin="I_C174_A0_P1_in" dir="in" link="Link13" />
|
||||||
|
<Link pin="O_C174_A0_P2_out" dir="out" link="Link14" />
|
||||||
|
</Algorithm>
|
||||||
|
<Algorithm name="ICSigma100Out2" friendlyname="170x\140x output " cell="Output2 " location="{X=890, Y=227} " Growth="0 " GrowthB="0">
|
||||||
|
<Link pin="I_C185_A0_P1_in" dir="in" link="Link14" />
|
||||||
|
</Algorithm>
|
||||||
|
<Algorithm name="ICSigma100Out1" friendlyname="170x\140x output " cell="Output1 " location="{X=877, Y=57} " Growth="0 " GrowthB="0">
|
||||||
|
<Link pin="I_C183_A0_P1_in" dir="in" link="Link15" />
|
||||||
|
</Algorithm>
|
||||||
|
</Schematic>
|
||||||
|
</IC>
|
||||||
|
</NetList>
|
||||||
@@ -0,0 +1,5 @@
|
|||||||
|
4,
|
||||||
|
5122,
|
||||||
|
4098,
|
||||||
|
26,
|
||||||
|
4,
|
||||||
@@ -0,0 +1,13 @@
|
|||||||
|
# ADAU1701 SigmaStudio export
|
||||||
|
|
||||||
|
SigmaStudio project **DigiRadio**, IC1 = ADAU1701.
|
||||||
|
|
||||||
|
Key files:
|
||||||
|
- `DigiRadio_IC_1.h` — program/param RAM data + `default_download_IC_1()`
|
||||||
|
- `DigiRadio_IC_1_REG.h` — register map
|
||||||
|
- `DigiRadio_IC_1_PARAM.h` — parameter handles (safeload, Slice 5+)
|
||||||
|
|
||||||
|
The ESP32 replays `default_download_IC_1()` over I2C on every boot via
|
||||||
|
`adau1701::Adau1701Driver` (see `components/drivers/adau1701/`).
|
||||||
|
|
||||||
|
I2C address: 7-bit `0x34` (ADDR0=ADDR1=GND), matching `board_pins.hpp`.
|
||||||
@@ -0,0 +1,54 @@
|
|||||||
|
/**
|
||||||
|
* @file SigmaStudioFW.h
|
||||||
|
* @brief Minimal SigmaStudio download helpers for ADAU1701 RAM boot.
|
||||||
|
*
|
||||||
|
* DigiRadio firmware — https://github.com/manvalan/DigiRadio
|
||||||
|
*
|
||||||
|
* Copyright 2026 Michele Bigi
|
||||||
|
* SPDX-License-Identifier: Apache-2.0
|
||||||
|
*
|
||||||
|
* Subset of the Analog Devices SigmaStudio export support library.
|
||||||
|
*
|
||||||
|
* @author Michele Bigi
|
||||||
|
* @date 2026-07-06
|
||||||
|
*/
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#ifdef __cplusplus
|
||||||
|
extern "C" {
|
||||||
|
#endif
|
||||||
|
|
||||||
|
typedef unsigned char ADI_REG_TYPE;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Bind the I2C port used by SIGMA_WRITE_REGISTER_BLOCK.
|
||||||
|
*
|
||||||
|
* @param port ESP-IDF I2C master port number.
|
||||||
|
* @param addr7 ADAU1701 7-bit I2C address (0x34 on DigiRadio).
|
||||||
|
*/
|
||||||
|
void sigma_studio_bind_i2c(int port, unsigned char addr7);
|
||||||
|
|
||||||
|
/** @brief Attach the I2C device handle created by Adau1701Driver. */
|
||||||
|
void sigma_studio_set_device(void* i2cDevHandle);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Write a contiguous register/data block to the ADAU1701.
|
||||||
|
*
|
||||||
|
* @param devAddress SigmaStudio device address (0x68 write addr).
|
||||||
|
* @param address 16-bit target address in DSP memory map.
|
||||||
|
* @param length Payload length in bytes.
|
||||||
|
* @param pData Payload bytes.
|
||||||
|
*/
|
||||||
|
void SIGMA_WRITE_REGISTER_BLOCK(unsigned char devAddress,
|
||||||
|
unsigned int address,
|
||||||
|
unsigned char length,
|
||||||
|
ADI_REG_TYPE* pData);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Run the DigiRadio SigmaStudio default download sequence.
|
||||||
|
*/
|
||||||
|
void adau1701_run_default_download(void);
|
||||||
|
|
||||||
|
#ifdef __cplusplus
|
||||||
|
}
|
||||||
|
#endif
|
||||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,26 @@
|
|||||||
|
/*
|
||||||
|
* File: defines.h
|
||||||
|
*
|
||||||
|
* Created: Monday, July 6, 2026 2:41:28 PM
|
||||||
|
* Description: DigiRadio IC default download data definitions.
|
||||||
|
*
|
||||||
|
* This software is distributed in the hope that it will be useful,
|
||||||
|
* but is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR
|
||||||
|
* CONDITIONS OF ANY KIND, without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
|
||||||
|
*
|
||||||
|
* This software may only be used to program products purchased from
|
||||||
|
* Analog Devices for incorporation by you into audio products that
|
||||||
|
* are intended for resale to audio product end users. This software
|
||||||
|
* may not be distributed whole or in any part to third parties.
|
||||||
|
*
|
||||||
|
* Copyright ©2026 Analog Devices, Inc. All rights reserved.
|
||||||
|
*/
|
||||||
|
#ifndef __DEFINES_H__
|
||||||
|
#define __DEFINES_H__
|
||||||
|
|
||||||
|
#define BufferSize_IC_1 9254
|
||||||
|
#define NumTransactions_IC_1 5
|
||||||
|
|
||||||
|
|
||||||
|
#endif
|
||||||
@@ -0,0 +1,20 @@
|
|||||||
|
# DigiRadio — companion-chip firmware assets
|
||||||
|
|
||||||
|
Binary and SigmaStudio exports loaded by the ESP32 at every boot.
|
||||||
|
|
||||||
|
| Directory | Chip | Contents |
|
||||||
|
|-----------|------|----------|
|
||||||
|
| `ADAU1701-Firmware/` | ADAU1701 SigmaDSP | SigmaStudio export (`DigiRadio_IC_1.h`, …) — RAM program |
|
||||||
|
| `Si4684-Firmware/` | Si4684 DAB+/FM tuner | ROM patch + DAB + FM application images (`.bin`) |
|
||||||
|
|
||||||
|
## Si4684 blobs
|
||||||
|
|
||||||
|
`rom_patch_016.bin` and `dab_firmware.bin` are extracted from the
|
||||||
|
[PE5PVB SI4684-DAB-Receiver](https://github.com/PE5PVB/SI4684-DAB-Receiver)
|
||||||
|
project. `fm_firmware.bin` comes from the Skyworks Si4684 eval firmware pack
|
||||||
|
(see `tools/fetch_si4684_firmware.py --si46xx-dir`).
|
||||||
|
|
||||||
|
## ADAU1701 export
|
||||||
|
|
||||||
|
Generated by Analog Devices SigmaStudio for the DigiRadio DSP schematic.
|
||||||
|
Loaded on every boot (no self-boot EEPROM on this board).
|
||||||
@@ -0,0 +1,43 @@
|
|||||||
|
# Si4684 firmware images
|
||||||
|
|
||||||
|
| File | Size (typ.) | Role |
|
||||||
|
|------|-------------|------|
|
||||||
|
| `rom_patch_016.bin` | 5796 | ROM patch / bootloader helper (HOST_LOAD before main image) |
|
||||||
|
| `dab_firmware.bin` | ~517 KB | DAB+ application image (PE5PVB / Skyworks BIF) |
|
||||||
|
| `fm_firmware.bin` | ~530 KB | FM application image (Skyworks `fm_radio_5_1_0.bin`, AN649 A10) |
|
||||||
|
|
||||||
|
Boot sequence: AN649 — POWER_UP → LOAD_INIT → HOST_LOAD(patch) → LOAD_INIT →
|
||||||
|
HOST_LOAD(image) → BOOT. DAB and FM use the same patch; only the application
|
||||||
|
image differs.
|
||||||
|
|
||||||
|
## Refresh blobs
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# DAB + patch from PE5PVB GitHub (always safe to re-run):
|
||||||
|
python3 tools/fetch_si4684_firmware.py --dab-only
|
||||||
|
|
||||||
|
# FM from Skyworks eval / dabpi si46xx_firmware folder:
|
||||||
|
python3 tools/fetch_si4684_firmware.py --si46xx-dir /path/to/si46xx_firmware
|
||||||
|
|
||||||
|
# FM extracted from uGreen DABBoard radio_cli (Files_v16.zip from ugreen.eu/downloads):
|
||||||
|
python3 tools/fetch_si4684_firmware.py --from-ugreen-radio-cli ~/Downloads/Files_v16.zip
|
||||||
|
|
||||||
|
# Patch + DAB + FM entirely from uGreen radio_cli (overrides PE5PVB DAB):
|
||||||
|
python3 tools/fetch_si4684_firmware.py --from-ugreen-radio-cli ~/Downloads/Files_v16.zip --ugreen-all
|
||||||
|
|
||||||
|
# FM extracted from a full SPI flash dump (TechniSat layout, dirb.me tech wiki):
|
||||||
|
python3 tools/fetch_si4684_firmware.py --flash-dump technisat_spi.bin
|
||||||
|
```
|
||||||
|
|
||||||
|
FM images are **not** redistributed with PE5PVB (DAB-only project). Obtain
|
||||||
|
`fm_radio_5_1_0.bin` from the Si4684 evaluation package (AN649 table 1),
|
||||||
|
community `si46xx_firmware/` folders used by [teknoid/dabpi](https://github.com/teknoid/dabpi),
|
||||||
|
or extract from a uGreen [DABBoard](https://ugreen.eu/downloads/) `radio_cli`
|
||||||
|
binary (proprietary — keep local, do not commit to public git).
|
||||||
|
|
||||||
|
Low-level conversion helper:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
python3 tools/extract_si4684_blob.py --copy /path/to/fm_radio_5_1_0.bin \\
|
||||||
|
--out Firmware/Si4684-Firmware/fm_firmware.bin
|
||||||
|
```
|
||||||
+9
-3
@@ -2,7 +2,7 @@
|
|||||||
|
|
||||||
Open-source Hi-Fi DAB+/FM receiver firmware for the ESP32-S3.
|
Open-source Hi-Fi DAB+/FM receiver firmware for the ESP32-S3.
|
||||||
|
|
||||||
**Status:** Slice 2 — secure store and Wi-Fi STA provisioning.
|
**Status:** Slice 3 — Si4684 + ADAU1701 boot at power-up (network from Slice 2).
|
||||||
|
|
||||||
## Quick start
|
## Quick start
|
||||||
|
|
||||||
@@ -37,11 +37,11 @@ Manual PDF (design + HTTP API + class reference):
|
|||||||
cd docs/manual && latexmk -lualatex manual.tex
|
cd docs/manual && latexmk -lualatex manual.tex
|
||||||
```
|
```
|
||||||
|
|
||||||
## HTTP API (fw 0.2.0)
|
## HTTP API (fw 0.3.0)
|
||||||
|
|
||||||
| Method | Path | Purpose |
|
| Method | Path | Purpose |
|
||||||
|--------|------|---------|
|
|--------|------|---------|
|
||||||
| GET | `/api/health` | `{"status":"ok","fw":"0.2.0"}` |
|
| GET | `/api/health` | `{"status":"ok","fw":"0.3.0"}` |
|
||||||
| POST | `/api/wifi` | Provision STA credentials; reboot on success |
|
| POST | `/api/wifi` | Provision STA credentials; reboot on success |
|
||||||
|
|
||||||
Full schemas, error tokens, and boot flow: [`docs/manual/ch-api.tex`](docs/manual/ch-api.tex).
|
Full schemas, error tokens, and boot flow: [`docs/manual/ch-api.tex`](docs/manual/ch-api.tex).
|
||||||
@@ -49,6 +49,12 @@ C++ signatures: generate with `doxygen Doxyfile` → `docs/api/html/index.html`.
|
|||||||
|
|
||||||
## Layout
|
## Layout
|
||||||
|
|
||||||
|
| Path | Role |
|
||||||
|
|------|------|
|
||||||
|
| `Firmware/` | Si4684 `.bin` blobs (DAB+FM) + ADAU1701 SigmaStudio export |
|
||||||
|
| `components/drivers/si4684/` | AN649 SPI boot driver |
|
||||||
|
| `components/drivers/adau1701/` | I2C SigmaStudio RAM download |
|
||||||
|
|
||||||
See [`AGENTS.md`](AGENTS.md) §12 and [`instructions.md`](instructions.md) for
|
See [`AGENTS.md`](AGENTS.md) §12 and [`instructions.md`](instructions.md) for
|
||||||
the authoritative repo layout, coding rules, and development roadmap.
|
the authoritative repo layout, coding rules, and development roadmap.
|
||||||
|
|
||||||
|
|||||||
@@ -7,6 +7,9 @@ idf_component_register(
|
|||||||
"src/WifiSsid.cpp"
|
"src/WifiSsid.cpp"
|
||||||
"src/WifiCredentials.cpp"
|
"src/WifiCredentials.cpp"
|
||||||
"src/WifiProvisionJson.cpp"
|
"src/WifiProvisionJson.cpp"
|
||||||
|
"src/EmbeddedBlobReader.cpp"
|
||||||
|
"src/TunerJson.cpp"
|
||||||
|
"src/FrequencyKHz.cpp"
|
||||||
INCLUDE_DIRS "include"
|
INCLUDE_DIRS "include"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,57 @@
|
|||||||
|
/**
|
||||||
|
* @file EmbeddedBlobReader.hpp
|
||||||
|
* @brief IFirmwareBlobReader over a contiguous embedded flash region.
|
||||||
|
*
|
||||||
|
* DigiRadio firmware — https://github.com/manvalan/DigiRadio
|
||||||
|
*
|
||||||
|
* Copyright 2026 Michele Bigi
|
||||||
|
* SPDX-License-Identifier: Apache-2.0
|
||||||
|
*
|
||||||
|
* @author Michele Bigi
|
||||||
|
* @date 2026-07-06
|
||||||
|
*/
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "core/IFirmwareBlobReader.hpp"
|
||||||
|
|
||||||
|
#include <cstddef>
|
||||||
|
#include <span>
|
||||||
|
|
||||||
|
namespace core {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief EmbeddedBlobReader — non-owning view of an embedded binary.
|
||||||
|
*
|
||||||
|
* @dname EmbeddedBlobReader
|
||||||
|
* @return n/a (type)
|
||||||
|
* @pubstate Borrows [data_, data_ + size_) for the reader lifetime.
|
||||||
|
*
|
||||||
|
* @author Michele Bigi
|
||||||
|
* @date 2026-07-06
|
||||||
|
*/
|
||||||
|
class EmbeddedBlobReader final : public IFirmwareBlobReader {
|
||||||
|
public:
|
||||||
|
/**
|
||||||
|
* @brief EmbeddedBlobReader — construct over a flash-backed range.
|
||||||
|
*
|
||||||
|
* @dname EmbeddedBlobReader
|
||||||
|
* @param data Start of embedded blob (e.g. linker symbol).
|
||||||
|
* @param size Length in bytes.
|
||||||
|
* @pubstate stores data_ and size_.
|
||||||
|
*
|
||||||
|
* @author Michele Bigi
|
||||||
|
* @date 2026-07-06
|
||||||
|
*/
|
||||||
|
EmbeddedBlobReader(const std::byte* data, std::size_t size);
|
||||||
|
|
||||||
|
[[nodiscard]] std::size_t size() const override;
|
||||||
|
|
||||||
|
[[nodiscard]] std::size_t read(std::size_t offset,
|
||||||
|
std::span<std::byte> dest) const override;
|
||||||
|
|
||||||
|
private:
|
||||||
|
const std::byte* data_;
|
||||||
|
std::size_t size_;
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace core
|
||||||
@@ -0,0 +1,73 @@
|
|||||||
|
/**
|
||||||
|
* @file FrequencyKHz.hpp
|
||||||
|
* @brief Strong type for FM centre frequency in kilohertz.
|
||||||
|
*
|
||||||
|
* DigiRadio firmware — https://github.com/manvalan/DigiRadio
|
||||||
|
*
|
||||||
|
* Copyright 2026 Michele Bigi
|
||||||
|
* SPDX-License-Identifier: Apache-2.0
|
||||||
|
*
|
||||||
|
* @author Michele Bigi
|
||||||
|
* @date 2026-07-06
|
||||||
|
*/
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "core/ParseError.hpp"
|
||||||
|
|
||||||
|
#include <cstdint>
|
||||||
|
#include <expected>
|
||||||
|
|
||||||
|
namespace core {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief FrequencyKHz — validated FM centre frequency (European band).
|
||||||
|
*
|
||||||
|
* @dname FrequencyKHz
|
||||||
|
* @return n/a (type)
|
||||||
|
* @pubstate Owns khz_ within 64\,000–108\,000 kHz. Immutable after construction.
|
||||||
|
*
|
||||||
|
* Parsed once at the HTTP boundary; trusted downstream by ITuner and drivers.
|
||||||
|
*
|
||||||
|
* @author Michele Bigi
|
||||||
|
* @date 2026-07-06
|
||||||
|
*/
|
||||||
|
class FrequencyKHz {
|
||||||
|
public:
|
||||||
|
/** Minimum valid FM frequency for DigiRadio (64.0 MHz). */
|
||||||
|
static constexpr std::uint32_t kMinKhz = 64000U;
|
||||||
|
/** Maximum valid FM frequency for DigiRadio (108.0 MHz). */
|
||||||
|
static constexpr std::uint32_t kMaxKhz = 108000U;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief tryFromKhz — validate an FM frequency at the boundary.
|
||||||
|
*
|
||||||
|
* @dname tryFromKhz
|
||||||
|
* @param khz Untrusted frequency in kilohertz from JSON input.
|
||||||
|
* @return FrequencyKHz on success, or ParseError::MissingField.
|
||||||
|
* @pubstate none
|
||||||
|
*
|
||||||
|
* @author Michele Bigi
|
||||||
|
* @date 2026-07-06
|
||||||
|
*/
|
||||||
|
[[nodiscard]] static std::expected<FrequencyKHz, ParseError> tryFromKhz(
|
||||||
|
std::uint32_t khz) noexcept;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief value — read the stored frequency in kilohertz.
|
||||||
|
*
|
||||||
|
* @dname value
|
||||||
|
* @return Validated centre frequency in kHz.
|
||||||
|
* @pubstate reads khz_.
|
||||||
|
*
|
||||||
|
* @author Michele Bigi
|
||||||
|
* @date 2026-07-06
|
||||||
|
*/
|
||||||
|
[[nodiscard]] std::uint32_t value() const noexcept;
|
||||||
|
|
||||||
|
private:
|
||||||
|
explicit FrequencyKHz(std::uint32_t khz) noexcept;
|
||||||
|
|
||||||
|
std::uint32_t khz_;
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace core
|
||||||
@@ -0,0 +1,72 @@
|
|||||||
|
/**
|
||||||
|
* @file IFirmwareBlobReader.hpp
|
||||||
|
* @brief Read-only streaming access to a firmware blob in flash.
|
||||||
|
*
|
||||||
|
* DigiRadio firmware — https://github.com/manvalan/DigiRadio
|
||||||
|
*
|
||||||
|
* Copyright 2026 Michele Bigi
|
||||||
|
* SPDX-License-Identifier: Apache-2.0
|
||||||
|
*
|
||||||
|
* @author Michele Bigi
|
||||||
|
* @date 2026-07-06
|
||||||
|
*/
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <cstddef>
|
||||||
|
#include <span>
|
||||||
|
|
||||||
|
namespace core {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief IFirmwareBlobReader — streaming firmware image access.
|
||||||
|
*
|
||||||
|
* @dname IFirmwareBlobReader
|
||||||
|
* @return n/a (type)
|
||||||
|
* @pubstate Implementations wrap embedded flash or test buffers. Used by
|
||||||
|
* Si4684Driver to HOST_LOAD in bounded chunks (AGENTS.md §7.1).
|
||||||
|
*
|
||||||
|
* @author Michele Bigi
|
||||||
|
* @date 2026-07-06
|
||||||
|
*/
|
||||||
|
class IFirmwareBlobReader {
|
||||||
|
public:
|
||||||
|
/**
|
||||||
|
* @brief ~IFirmwareBlobReader — virtual destructor.
|
||||||
|
*
|
||||||
|
* @dname ~IFirmwareBlobReader
|
||||||
|
* @pubstate n/a
|
||||||
|
*
|
||||||
|
* @author Michele Bigi
|
||||||
|
* @date 2026-07-06
|
||||||
|
*/
|
||||||
|
virtual ~IFirmwareBlobReader() = default;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief size — total blob length in bytes.
|
||||||
|
*
|
||||||
|
* @dname size
|
||||||
|
* @return Blob size.
|
||||||
|
* @pubstate reads backing storage.
|
||||||
|
*
|
||||||
|
* @author Michele Bigi
|
||||||
|
* @date 2026-07-06
|
||||||
|
*/
|
||||||
|
[[nodiscard]] virtual std::size_t size() const = 0;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief read — copy bytes from the blob at an offset.
|
||||||
|
*
|
||||||
|
* @dname read
|
||||||
|
* @param offset Byte offset from the start of the blob.
|
||||||
|
* @param dest Destination buffer; truncated at end of blob.
|
||||||
|
* @return Number of bytes copied into dest.
|
||||||
|
* @pubstate reads backing storage.
|
||||||
|
*
|
||||||
|
* @author Michele Bigi
|
||||||
|
* @date 2026-07-06
|
||||||
|
*/
|
||||||
|
[[nodiscard]] virtual std::size_t read(std::size_t offset,
|
||||||
|
std::span<std::byte> dest) const = 0;
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace core
|
||||||
@@ -0,0 +1,170 @@
|
|||||||
|
/**
|
||||||
|
* @file ITuner.hpp
|
||||||
|
* @brief Abstract tuner driver boundary (host-testable).
|
||||||
|
*
|
||||||
|
* DigiRadio firmware — https://github.com/manvalan/DigiRadio
|
||||||
|
*
|
||||||
|
* Copyright 2026 Michele Bigi
|
||||||
|
* SPDX-License-Identifier: Apache-2.0
|
||||||
|
*
|
||||||
|
* @author Michele Bigi
|
||||||
|
* @date 2026-07-06
|
||||||
|
*/
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "core/FrequencyKHz.hpp"
|
||||||
|
#include "core/SeekDirection.hpp"
|
||||||
|
#include "core/TunerBand.hpp"
|
||||||
|
#include "core/TunerError.hpp"
|
||||||
|
#include "core/TunerStatus.hpp"
|
||||||
|
|
||||||
|
#include <cstdint>
|
||||||
|
#include <expected>
|
||||||
|
#include <vector>
|
||||||
|
|
||||||
|
namespace core {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief ITuner — hardware abstraction for DAB/FM tuning.
|
||||||
|
*
|
||||||
|
* @dname ITuner
|
||||||
|
* @return n/a (type)
|
||||||
|
* @pubstate Implemented by si4684::Si4684Tuner on device; fakes in host tests.
|
||||||
|
* No public data members.
|
||||||
|
*
|
||||||
|
* Services depend on this interface, not on SPI details. All methods return
|
||||||
|
* std::expected with TunerError on failure.
|
||||||
|
*
|
||||||
|
* @author Michele Bigi
|
||||||
|
* @date 2026-07-06
|
||||||
|
*/
|
||||||
|
class ITuner {
|
||||||
|
public:
|
||||||
|
virtual ~ITuner() = default;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief boot — load the requested band application image.
|
||||||
|
*
|
||||||
|
* @dname boot
|
||||||
|
* @param band DAB or FM image to load.
|
||||||
|
* @return Ok on success, or TunerError::HardwareFailed / NotBooted.
|
||||||
|
* @pubstate none (implementation-defined driver state).
|
||||||
|
*
|
||||||
|
* @author Michele Bigi
|
||||||
|
* @date 2026-07-06
|
||||||
|
*/
|
||||||
|
[[nodiscard]] virtual std::expected<void, TunerError> boot(
|
||||||
|
TunerBand band) = 0;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief currentBand — read the loaded application band.
|
||||||
|
*
|
||||||
|
* @dname currentBand
|
||||||
|
* @return Active TunerBand, or TunerError::NotBooted.
|
||||||
|
* @pubstate reads driver boot state.
|
||||||
|
*
|
||||||
|
* @author Michele Bigi
|
||||||
|
* @date 2026-07-06
|
||||||
|
*/
|
||||||
|
[[nodiscard]] virtual std::expected<TunerBand, TunerError> currentBand()
|
||||||
|
const = 0;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief readStatus — snapshot lock, metrics, and tune target.
|
||||||
|
*
|
||||||
|
* @dname readStatus
|
||||||
|
* @return TunerStatus on success, or a TunerError.
|
||||||
|
* @pubstate reads driver metrics; may refresh RSQ/DIGRAD.
|
||||||
|
*
|
||||||
|
* @author Michele Bigi
|
||||||
|
* @date 2026-07-06
|
||||||
|
*/
|
||||||
|
[[nodiscard]] virtual std::expected<TunerStatus, TunerError> readStatus() = 0;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief tuneDab — select a Band III ensemble by index.
|
||||||
|
*
|
||||||
|
* @dname tuneDab
|
||||||
|
* @param freqIndex Ensemble index 0–37.
|
||||||
|
* @return Ok on success, or WrongBand / TuneFailed / NotBooted.
|
||||||
|
* @pubstate writes last tune target in the adapter.
|
||||||
|
*
|
||||||
|
* @author Michele Bigi
|
||||||
|
* @date 2026-07-06
|
||||||
|
*/
|
||||||
|
[[nodiscard]] virtual std::expected<void, TunerError> tuneDab(
|
||||||
|
std::uint8_t freqIndex) = 0;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief tuneFm — tune to an FM centre frequency.
|
||||||
|
*
|
||||||
|
* @dname tuneFm
|
||||||
|
* @param frequency Validated FM centre frequency.
|
||||||
|
* @return Ok on success, or WrongBand / TuneFailed / NotBooted.
|
||||||
|
* @pubstate writes last tune target in the adapter.
|
||||||
|
*
|
||||||
|
* @author Michele Bigi
|
||||||
|
* @date 2026-07-06
|
||||||
|
*/
|
||||||
|
[[nodiscard]] virtual std::expected<void, TunerError> tuneFm(
|
||||||
|
FrequencyKHz frequency) = 0;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief seekFm — seek to the next valid FM station.
|
||||||
|
*
|
||||||
|
* @dname seekFm
|
||||||
|
* @param direction Up or Down scan direction.
|
||||||
|
* @return New centre frequency, or a TunerError.
|
||||||
|
* @pubstate updates last tune target on success.
|
||||||
|
*
|
||||||
|
* @author Michele Bigi
|
||||||
|
* @date 2026-07-06
|
||||||
|
*/
|
||||||
|
[[nodiscard]] virtual std::expected<FrequencyKHz, TunerError> seekFm(
|
||||||
|
SeekDirection direction) = 0;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief listDabServices — fetch programmes for the current ensemble.
|
||||||
|
*
|
||||||
|
* @dname listDabServices
|
||||||
|
* @return Service entries, ServiceListEmpty, WrongBand, or NotBooted.
|
||||||
|
* @pubstate reads DAB service list from the driver.
|
||||||
|
*
|
||||||
|
* @author Michele Bigi
|
||||||
|
* @date 2026-07-06
|
||||||
|
*/
|
||||||
|
[[nodiscard]] virtual std::expected<std::vector<TunerServiceEntry>,
|
||||||
|
TunerError>
|
||||||
|
listDabServices() = 0;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief playDabService — start DAB audio for a programme.
|
||||||
|
*
|
||||||
|
* @dname playDabService
|
||||||
|
* @param serviceId Selected service identifier.
|
||||||
|
* @param componentId Audio component within the service.
|
||||||
|
* @return Ok on success, or WrongBand / HardwareFailed / NotBooted.
|
||||||
|
* @pubstate starts digital audio output on the tuner I2S port.
|
||||||
|
*
|
||||||
|
* @author Michele Bigi
|
||||||
|
* @date 2026-07-06
|
||||||
|
*/
|
||||||
|
[[nodiscard]] virtual std::expected<void, TunerError> playDabService(
|
||||||
|
std::uint32_t serviceId, std::uint32_t componentId) = 0;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief setVolume — set tuner output attenuation.
|
||||||
|
*
|
||||||
|
* @dname setVolume
|
||||||
|
* @param level Attenuator 0–63.
|
||||||
|
* @return Ok on success, or HardwareFailed / NotBooted.
|
||||||
|
* @pubstate writes volume property on the driver.
|
||||||
|
*
|
||||||
|
* @author Michele Bigi
|
||||||
|
* @date 2026-07-06
|
||||||
|
*/
|
||||||
|
[[nodiscard]] virtual std::expected<void, TunerError> setVolume(
|
||||||
|
std::uint8_t level) = 0;
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace core
|
||||||
@@ -0,0 +1,31 @@
|
|||||||
|
/**
|
||||||
|
* @file SeekDirection.hpp
|
||||||
|
* @brief FM seek direction selector (core domain).
|
||||||
|
*
|
||||||
|
* DigiRadio firmware — https://github.com/manvalan/DigiRadio
|
||||||
|
*
|
||||||
|
* Copyright 2026 Michele Bigi
|
||||||
|
* SPDX-License-Identifier: Apache-2.0
|
||||||
|
*
|
||||||
|
* @author Michele Bigi
|
||||||
|
* @date 2026-07-06
|
||||||
|
*/
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
namespace core {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief SeekDirection — FM seek scan direction.
|
||||||
|
*
|
||||||
|
* @dname SeekDirection
|
||||||
|
* @return n/a (type)
|
||||||
|
* @pubstate n/a
|
||||||
|
*
|
||||||
|
* Replaces bare bool parameters in public tuner APIs (AGENTS.md §2.1).
|
||||||
|
*
|
||||||
|
* @author Michele Bigi
|
||||||
|
* @date 2026-07-06
|
||||||
|
*/
|
||||||
|
enum class SeekDirection { Up, Down };
|
||||||
|
|
||||||
|
} // namespace core
|
||||||
@@ -0,0 +1,29 @@
|
|||||||
|
/**
|
||||||
|
* @file TunerBand.hpp
|
||||||
|
* @brief Strong band selector for tuner operations (core domain).
|
||||||
|
*
|
||||||
|
* DigiRadio firmware — https://github.com/manvalan/DigiRadio
|
||||||
|
*
|
||||||
|
* Copyright 2026 Michele Bigi
|
||||||
|
* SPDX-License-Identifier: Apache-2.0
|
||||||
|
*
|
||||||
|
* @author Michele Bigi
|
||||||
|
* @date 2026-07-06
|
||||||
|
*/
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
namespace core {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief TunerBand — active RF application (DAB or FM image).
|
||||||
|
*
|
||||||
|
* @dname TunerBand
|
||||||
|
* @return n/a (type)
|
||||||
|
* @pubstate n/a
|
||||||
|
*
|
||||||
|
* @author Michele Bigi
|
||||||
|
* @date 2026-07-06
|
||||||
|
*/
|
||||||
|
enum class TunerBand { Dab, Fm };
|
||||||
|
|
||||||
|
} // namespace core
|
||||||
@@ -0,0 +1,36 @@
|
|||||||
|
/**
|
||||||
|
* @file TunerError.hpp
|
||||||
|
* @brief Typed errors for tuner service and ITuner adapters.
|
||||||
|
*
|
||||||
|
* DigiRadio firmware — https://github.com/manvalan/DigiRadio
|
||||||
|
*
|
||||||
|
* Copyright 2026 Michele Bigi
|
||||||
|
* SPDX-License-Identifier: Apache-2.0
|
||||||
|
*
|
||||||
|
* @author Michele Bigi
|
||||||
|
* @date 2026-07-06
|
||||||
|
*/
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
namespace core {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief TunerError — failure causes for tuner operations.
|
||||||
|
*
|
||||||
|
* @dname TunerError
|
||||||
|
* @return n/a (type)
|
||||||
|
* @pubstate Stable error set mapped from driver failures at the service boundary.
|
||||||
|
*
|
||||||
|
* @author Michele Bigi
|
||||||
|
* @date 2026-07-06
|
||||||
|
*/
|
||||||
|
enum class TunerError {
|
||||||
|
NotBooted,
|
||||||
|
WrongBand,
|
||||||
|
HardwareFailed,
|
||||||
|
TuneFailed,
|
||||||
|
ServiceListEmpty,
|
||||||
|
InvalidInput,
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace core
|
||||||
@@ -0,0 +1,131 @@
|
|||||||
|
/**
|
||||||
|
* @file TunerJson.hpp
|
||||||
|
* @brief JSON parse/serialise for tuner API (pure core).
|
||||||
|
*
|
||||||
|
* DigiRadio firmware — https://github.com/manvalan/DigiRadio
|
||||||
|
*
|
||||||
|
* Copyright 2026 Michele Bigi
|
||||||
|
* SPDX-License-Identifier: Apache-2.0
|
||||||
|
*
|
||||||
|
* @author Michele Bigi
|
||||||
|
* @date 2026-07-06
|
||||||
|
*/
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "core/FrequencyKHz.hpp"
|
||||||
|
#include "core/ParseError.hpp"
|
||||||
|
#include "core/TunerBand.hpp"
|
||||||
|
#include "core/TunerStatus.hpp"
|
||||||
|
|
||||||
|
#include <cstdint>
|
||||||
|
#include <expected>
|
||||||
|
#include <optional>
|
||||||
|
#include <string>
|
||||||
|
#include <string_view>
|
||||||
|
#include <vector>
|
||||||
|
|
||||||
|
namespace core {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief TunerTuneRequest — parsed POST /api/tuner/tune body.
|
||||||
|
*
|
||||||
|
* @dname TunerTuneRequest
|
||||||
|
* @return n/a (type)
|
||||||
|
* @pubstate Plain DTO filled by parseTunerTuneJson at the HTTP boundary.
|
||||||
|
*
|
||||||
|
* @author Michele Bigi
|
||||||
|
* @date 2026-07-06
|
||||||
|
*/
|
||||||
|
struct TunerTuneRequest {
|
||||||
|
TunerBand band; ///< Target band (Dab or Fm).
|
||||||
|
std::uint8_t dabFreqIndex; ///< Band III ensemble index (0–37) when band is Dab.
|
||||||
|
std::optional<FrequencyKHz> fmFrequency; ///< FM centre frequency when band is Fm.
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief TunerPlayRequest — parsed POST /api/tuner/play body.
|
||||||
|
*
|
||||||
|
* @dname TunerPlayRequest
|
||||||
|
* @return n/a (type)
|
||||||
|
* @pubstate Plain DTO filled by parseTunerPlayJson at the HTTP boundary.
|
||||||
|
*
|
||||||
|
* @author Michele Bigi
|
||||||
|
* @date 2026-07-06
|
||||||
|
*/
|
||||||
|
struct TunerPlayRequest {
|
||||||
|
std::uint32_t serviceId; ///< DAB service identifier from the ensemble list.
|
||||||
|
std::uint32_t componentId; ///< Audio component within the service.
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief serializeTunerStatusJson — serialise a tuner snapshot for GET status.
|
||||||
|
*
|
||||||
|
* @dname serializeTunerStatusJson
|
||||||
|
* @param status Domain snapshot from TunerService::refreshStatus().
|
||||||
|
* @return JSON object string for the HTTP response body.
|
||||||
|
* @pubstate none
|
||||||
|
*
|
||||||
|
* @author Michele Bigi
|
||||||
|
* @date 2026-07-06
|
||||||
|
*/
|
||||||
|
[[nodiscard]] std::string serializeTunerStatusJson(const TunerStatus& status);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief serializeTunerServicesJson — serialise the DAB service list.
|
||||||
|
*
|
||||||
|
* @dname serializeTunerServicesJson
|
||||||
|
* @param services Programme entries for the current ensemble.
|
||||||
|
* @return JSON object string with a services array.
|
||||||
|
* @pubstate none
|
||||||
|
*
|
||||||
|
* @author Michele Bigi
|
||||||
|
* @date 2026-07-06
|
||||||
|
*/
|
||||||
|
[[nodiscard]] std::string serializeTunerServicesJson(
|
||||||
|
const std::vector<TunerServiceEntry>& services);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief serializeTunerErrorJson — serialise a tuner API error response.
|
||||||
|
*
|
||||||
|
* @dname serializeTunerErrorJson
|
||||||
|
* @param reason Short machine-readable cause (never a secret).
|
||||||
|
* @return JSON object string with status error and reason fields.
|
||||||
|
* @pubstate none
|
||||||
|
*
|
||||||
|
* @author Michele Bigi
|
||||||
|
* @date 2026-07-06
|
||||||
|
*/
|
||||||
|
[[nodiscard]] std::string serializeTunerErrorJson(const char* reason);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief parseTunerTuneJson — validate POST /api/tuner/tune body.
|
||||||
|
*
|
||||||
|
* @dname parseTunerTuneJson
|
||||||
|
* @param json Untrusted request body from the HTTP handler.
|
||||||
|
* @return TunerTuneRequest on success, or a ParseError.
|
||||||
|
* @pubstate none
|
||||||
|
*
|
||||||
|
* Rejects malformed JSON, missing fields, and out-of-range frequencies
|
||||||
|
* before any driver call.
|
||||||
|
*
|
||||||
|
* @author Michele Bigi
|
||||||
|
* @date 2026-07-06
|
||||||
|
*/
|
||||||
|
[[nodiscard]] std::expected<TunerTuneRequest, ParseError> parseTunerTuneJson(
|
||||||
|
std::string_view json);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief parseTunerPlayJson — validate POST /api/tuner/play body.
|
||||||
|
*
|
||||||
|
* @dname parseTunerPlayJson
|
||||||
|
* @param json Untrusted request body from the HTTP handler.
|
||||||
|
* @return TunerPlayRequest on success, or a ParseError.
|
||||||
|
* @pubstate none
|
||||||
|
*
|
||||||
|
* @author Michele Bigi
|
||||||
|
* @date 2026-07-06
|
||||||
|
*/
|
||||||
|
[[nodiscard]] std::expected<TunerPlayRequest, ParseError> parseTunerPlayJson(
|
||||||
|
std::string_view json);
|
||||||
|
|
||||||
|
} // namespace core
|
||||||
@@ -0,0 +1,65 @@
|
|||||||
|
/**
|
||||||
|
* @file TunerStatus.hpp
|
||||||
|
* @brief Tuner status DTO for API and UI (pure core).
|
||||||
|
*
|
||||||
|
* DigiRadio firmware — https://github.com/manvalan/DigiRadio
|
||||||
|
*
|
||||||
|
* Copyright 2026 Michele Bigi
|
||||||
|
* SPDX-License-Identifier: Apache-2.0
|
||||||
|
*
|
||||||
|
* @author Michele Bigi
|
||||||
|
* @date 2026-07-06
|
||||||
|
*/
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "core/FrequencyKHz.hpp"
|
||||||
|
#include "core/TunerBand.hpp"
|
||||||
|
|
||||||
|
#include <array>
|
||||||
|
#include <cstdint>
|
||||||
|
#include <optional>
|
||||||
|
|
||||||
|
namespace core {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief TunerServiceEntry — one DAB programme in the current ensemble.
|
||||||
|
*
|
||||||
|
* @dname TunerServiceEntry
|
||||||
|
* @return n/a (type)
|
||||||
|
* @pubstate Plain DTO returned by ITuner::listDabServices().
|
||||||
|
*
|
||||||
|
* @author Michele Bigi
|
||||||
|
* @date 2026-07-06
|
||||||
|
*/
|
||||||
|
struct TunerServiceEntry {
|
||||||
|
std::uint32_t serviceId; ///< DAB service identifier.
|
||||||
|
std::uint32_t componentId; ///< Audio component within the service.
|
||||||
|
std::array<char, 17> label; ///< UTF-8 programme label, NUL-terminated.
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief TunerStatus — snapshot for GET /api/tuner/status.
|
||||||
|
*
|
||||||
|
* @dname TunerStatus
|
||||||
|
* @return n/a (type)
|
||||||
|
* @pubstate Plain DTO built by ITuner::readStatus(); serialised by
|
||||||
|
* serializeTunerStatusJson(). Band-specific fields are optional.
|
||||||
|
*
|
||||||
|
* @author Michele Bigi
|
||||||
|
* @date 2026-07-06
|
||||||
|
*/
|
||||||
|
struct TunerStatus {
|
||||||
|
bool booted; ///< Companion tuner boot completed.
|
||||||
|
TunerBand band; ///< Active loaded application band.
|
||||||
|
bool locked; ///< RF lock / valid signal.
|
||||||
|
std::uint8_t volume; ///< Attenuator level 0–63.
|
||||||
|
std::optional<std::uint8_t> dabFreqIndex; ///< Current Band III ensemble index.
|
||||||
|
std::optional<std::uint8_t> dabFicQuality; ///< FIC quality 0–100 when DAB.
|
||||||
|
std::optional<std::int8_t> dabCnrDb; ///< CNR in dB when DAB.
|
||||||
|
std::optional<FrequencyKHz> fmFrequency; ///< Tuned FM centre frequency.
|
||||||
|
std::optional<std::int8_t> fmRssiDbuV; ///< FM RSSI in dBµV.
|
||||||
|
std::optional<std::int8_t> fmSnrDb; ///< FM SNR in dB.
|
||||||
|
std::optional<bool> fmStereo; ///< FM stereo pilot detected.
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace core
|
||||||
@@ -0,0 +1,44 @@
|
|||||||
|
/**
|
||||||
|
* @file EmbeddedBlobReader.cpp
|
||||||
|
* @brief EmbeddedBlobReader implementation.
|
||||||
|
*
|
||||||
|
* DigiRadio firmware — https://github.com/manvalan/DigiRadio
|
||||||
|
*
|
||||||
|
* Copyright 2026 Michele Bigi
|
||||||
|
* SPDX-License-Identifier: Apache-2.0
|
||||||
|
*
|
||||||
|
* @author Michele Bigi
|
||||||
|
* @date 2026-07-06
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include "core/EmbeddedBlobReader.hpp"
|
||||||
|
|
||||||
|
#include <algorithm>
|
||||||
|
#include <cstring>
|
||||||
|
|
||||||
|
namespace core {
|
||||||
|
|
||||||
|
EmbeddedBlobReader::EmbeddedBlobReader(const std::byte* data, std::size_t size)
|
||||||
|
: data_(data)
|
||||||
|
, size_(size)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
std::size_t EmbeddedBlobReader::size() const
|
||||||
|
{
|
||||||
|
return size_;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::size_t EmbeddedBlobReader::read(std::size_t offset,
|
||||||
|
std::span<std::byte> dest) const
|
||||||
|
{
|
||||||
|
if (offset >= size_ || dest.empty()) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
const std::size_t available = size_ - offset;
|
||||||
|
const std::size_t toCopy = std::min(available, dest.size());
|
||||||
|
std::memcpy(dest.data(), data_ + offset, toCopy);
|
||||||
|
return toCopy;
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace core
|
||||||
@@ -0,0 +1,37 @@
|
|||||||
|
/**
|
||||||
|
* @file FrequencyKHz.cpp
|
||||||
|
* @brief FrequencyKHz implementation.
|
||||||
|
*
|
||||||
|
* DigiRadio firmware — https://github.com/manvalan/DigiRadio
|
||||||
|
*
|
||||||
|
* Copyright 2026 Michele Bigi
|
||||||
|
* SPDX-License-Identifier: Apache-2.0
|
||||||
|
*
|
||||||
|
* @author Michele Bigi
|
||||||
|
* @date 2026-07-06
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include "core/FrequencyKHz.hpp"
|
||||||
|
|
||||||
|
namespace core {
|
||||||
|
|
||||||
|
FrequencyKHz::FrequencyKHz(std::uint32_t khz) noexcept
|
||||||
|
: khz_(khz)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
std::expected<FrequencyKHz, ParseError> FrequencyKHz::tryFromKhz(
|
||||||
|
std::uint32_t khz) noexcept
|
||||||
|
{
|
||||||
|
if (khz < kMinKhz || khz > kMaxKhz) {
|
||||||
|
return std::unexpected(ParseError::MissingField);
|
||||||
|
}
|
||||||
|
return FrequencyKHz(khz);
|
||||||
|
}
|
||||||
|
|
||||||
|
std::uint32_t FrequencyKHz::value() const noexcept
|
||||||
|
{
|
||||||
|
return khz_;
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace core
|
||||||
@@ -0,0 +1,198 @@
|
|||||||
|
/**
|
||||||
|
* @file TunerJson.cpp
|
||||||
|
* @brief TunerJson implementation.
|
||||||
|
*
|
||||||
|
* DigiRadio firmware — https://github.com/manvalan/DigiRadio
|
||||||
|
*
|
||||||
|
* Copyright 2026 Michele Bigi
|
||||||
|
* SPDX-License-Identifier: Apache-2.0
|
||||||
|
*
|
||||||
|
* @author Michele Bigi
|
||||||
|
* @date 2026-07-06
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include "core/TunerJson.hpp"
|
||||||
|
|
||||||
|
#include <cstdlib>
|
||||||
|
#include <sstream>
|
||||||
|
|
||||||
|
namespace core {
|
||||||
|
|
||||||
|
namespace {
|
||||||
|
|
||||||
|
[[nodiscard]] std::string_view extractJsonString(std::string_view json,
|
||||||
|
std::string_view key)
|
||||||
|
{
|
||||||
|
const std::string needle =
|
||||||
|
std::string("\"") + std::string(key) + "\":\"";
|
||||||
|
const std::size_t start = json.find(needle);
|
||||||
|
if (start == std::string_view::npos) {
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
const std::size_t valueStart = start + needle.size();
|
||||||
|
const std::size_t valueEnd = json.find('"', valueStart);
|
||||||
|
if (valueEnd == std::string_view::npos) {
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
return json.substr(valueStart, valueEnd - valueStart);
|
||||||
|
}
|
||||||
|
|
||||||
|
[[nodiscard]] bool extractJsonUint(std::string_view json,
|
||||||
|
std::string_view key,
|
||||||
|
unsigned long& out)
|
||||||
|
{
|
||||||
|
const std::string needle =
|
||||||
|
std::string("\"") + std::string(key) + "\":";
|
||||||
|
const std::size_t start = json.find(needle);
|
||||||
|
if (start == std::string_view::npos) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
const std::size_t valueStart = start + needle.size();
|
||||||
|
char* end = nullptr;
|
||||||
|
out = std::strtoul(json.data() + valueStart, &end, 10);
|
||||||
|
return end != json.data() + valueStart;
|
||||||
|
}
|
||||||
|
|
||||||
|
[[nodiscard]] const char* bandToken(TunerBand band) noexcept
|
||||||
|
{
|
||||||
|
return band == TunerBand::Fm ? "fm" : "dab";
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace
|
||||||
|
|
||||||
|
std::string serializeTunerStatusJson(const TunerStatus& status)
|
||||||
|
{
|
||||||
|
std::ostringstream out;
|
||||||
|
out << "{\"booted\":" << (status.booted ? "true" : "false")
|
||||||
|
<< ",\"band\":\"" << bandToken(status.band) << "\""
|
||||||
|
<< ",\"locked\":" << (status.locked ? "true" : "false")
|
||||||
|
<< ",\"volume\":" << static_cast<unsigned>(status.volume);
|
||||||
|
|
||||||
|
if (status.band == TunerBand::Dab) {
|
||||||
|
out << ",\"dab\":{";
|
||||||
|
if (status.dabFreqIndex) {
|
||||||
|
out << "\"freq_index\":" << static_cast<unsigned>(*status.dabFreqIndex);
|
||||||
|
}
|
||||||
|
if (status.dabFicQuality) {
|
||||||
|
if (status.dabFreqIndex) {
|
||||||
|
out << ',';
|
||||||
|
}
|
||||||
|
out << "\"fic_quality\":" << static_cast<unsigned>(*status.dabFicQuality);
|
||||||
|
}
|
||||||
|
if (status.dabCnrDb) {
|
||||||
|
out << ",\"cnr_db\":" << static_cast<int>(*status.dabCnrDb);
|
||||||
|
}
|
||||||
|
out << "},\"fm\":null";
|
||||||
|
} else {
|
||||||
|
out << ",\"fm\":{";
|
||||||
|
if (status.fmFrequency) {
|
||||||
|
out << "\"frequency_khz\":" << status.fmFrequency->value();
|
||||||
|
}
|
||||||
|
if (status.fmRssiDbuV) {
|
||||||
|
if (status.fmFrequency) {
|
||||||
|
out << ',';
|
||||||
|
}
|
||||||
|
out << "\"rssi_dbuv\":" << static_cast<int>(*status.fmRssiDbuV);
|
||||||
|
}
|
||||||
|
if (status.fmSnrDb) {
|
||||||
|
out << ",\"snr_db\":" << static_cast<int>(*status.fmSnrDb);
|
||||||
|
}
|
||||||
|
if (status.fmStereo) {
|
||||||
|
out << ",\"stereo\":" << (*status.fmStereo ? "true" : "false");
|
||||||
|
}
|
||||||
|
out << "},\"dab\":null";
|
||||||
|
}
|
||||||
|
out << '}';
|
||||||
|
return out.str();
|
||||||
|
}
|
||||||
|
|
||||||
|
std::string serializeTunerServicesJson(
|
||||||
|
const std::vector<TunerServiceEntry>& services)
|
||||||
|
{
|
||||||
|
std::ostringstream out;
|
||||||
|
out << "{\"services\":[";
|
||||||
|
for (std::size_t i = 0; i < services.size(); ++i) {
|
||||||
|
if (i > 0U) {
|
||||||
|
out << ',';
|
||||||
|
}
|
||||||
|
const auto& s = services[i];
|
||||||
|
out << "{\"service_id\":" << s.serviceId
|
||||||
|
<< ",\"component_id\":" << s.componentId << ",\"label\":\"";
|
||||||
|
for (char c : s.label) {
|
||||||
|
if (c == '\0') {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
if (c == '"' || c == '\\') {
|
||||||
|
out << '\\';
|
||||||
|
}
|
||||||
|
out << c;
|
||||||
|
}
|
||||||
|
out << "\"}";
|
||||||
|
}
|
||||||
|
out << "]}";
|
||||||
|
return out.str();
|
||||||
|
}
|
||||||
|
|
||||||
|
std::string serializeTunerErrorJson(const char* reason)
|
||||||
|
{
|
||||||
|
return std::string("{\"status\":\"error\",\"reason\":\"") + reason + "\"}";
|
||||||
|
}
|
||||||
|
|
||||||
|
std::expected<TunerTuneRequest, ParseError> parseTunerTuneJson(
|
||||||
|
std::string_view json)
|
||||||
|
{
|
||||||
|
if (json.find('{') == std::string_view::npos) {
|
||||||
|
return std::unexpected(ParseError::InvalidJson);
|
||||||
|
}
|
||||||
|
|
||||||
|
const std::string_view band = extractJsonString(json, "band");
|
||||||
|
if (band.empty()) {
|
||||||
|
return std::unexpected(ParseError::MissingField);
|
||||||
|
}
|
||||||
|
|
||||||
|
TunerTuneRequest req = {};
|
||||||
|
if (band == "dab") {
|
||||||
|
req.band = TunerBand::Dab;
|
||||||
|
unsigned long idx = 0U;
|
||||||
|
if (!extractJsonUint(json, "freq_index", idx) || idx > 37U) {
|
||||||
|
return std::unexpected(ParseError::MissingField);
|
||||||
|
}
|
||||||
|
req.dabFreqIndex = static_cast<std::uint8_t>(idx);
|
||||||
|
} else if (band == "fm") {
|
||||||
|
req.band = TunerBand::Fm;
|
||||||
|
unsigned long khz = 0U;
|
||||||
|
if (!extractJsonUint(json, "frequency_khz", khz)) {
|
||||||
|
return std::unexpected(ParseError::MissingField);
|
||||||
|
}
|
||||||
|
auto freq = FrequencyKHz::tryFromKhz(static_cast<std::uint32_t>(khz));
|
||||||
|
if (!freq) {
|
||||||
|
return std::unexpected(freq.error());
|
||||||
|
}
|
||||||
|
req.fmFrequency = *freq;
|
||||||
|
} else {
|
||||||
|
return std::unexpected(ParseError::InvalidJson);
|
||||||
|
}
|
||||||
|
return req;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::expected<TunerPlayRequest, ParseError> parseTunerPlayJson(
|
||||||
|
std::string_view json)
|
||||||
|
{
|
||||||
|
if (json.find('{') == std::string_view::npos) {
|
||||||
|
return std::unexpected(ParseError::InvalidJson);
|
||||||
|
}
|
||||||
|
|
||||||
|
unsigned long sid = 0U;
|
||||||
|
unsigned long cid = 0U;
|
||||||
|
if (!extractJsonUint(json, "service_id", sid)
|
||||||
|
|| !extractJsonUint(json, "component_id", cid)) {
|
||||||
|
return std::unexpected(ParseError::MissingField);
|
||||||
|
}
|
||||||
|
|
||||||
|
TunerPlayRequest req = {};
|
||||||
|
req.serviceId = static_cast<std::uint32_t>(sid);
|
||||||
|
req.componentId = static_cast<std::uint32_t>(cid);
|
||||||
|
return req;
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace core
|
||||||
@@ -19,6 +19,9 @@ add_library(digiradio_core STATIC
|
|||||||
"${CORE_SRC_DIR}/WifiSsid.cpp"
|
"${CORE_SRC_DIR}/WifiSsid.cpp"
|
||||||
"${CORE_SRC_DIR}/WifiCredentials.cpp"
|
"${CORE_SRC_DIR}/WifiCredentials.cpp"
|
||||||
"${CORE_SRC_DIR}/WifiProvisionJson.cpp"
|
"${CORE_SRC_DIR}/WifiProvisionJson.cpp"
|
||||||
|
"${CORE_SRC_DIR}/EmbeddedBlobReader.cpp"
|
||||||
|
"${CORE_SRC_DIR}/TunerJson.cpp"
|
||||||
|
"${CORE_SRC_DIR}/FrequencyKHz.cpp"
|
||||||
)
|
)
|
||||||
target_include_directories(digiradio_core PUBLIC
|
target_include_directories(digiradio_core PUBLIC
|
||||||
"${CMAKE_CURRENT_SOURCE_DIR}/../include"
|
"${CMAKE_CURRENT_SOURCE_DIR}/../include"
|
||||||
@@ -31,3 +34,15 @@ add_test(NAME health_status_test COMMAND health_status_test)
|
|||||||
add_executable(wifi_provision_test wifi_provision_test.cpp)
|
add_executable(wifi_provision_test wifi_provision_test.cpp)
|
||||||
target_link_libraries(wifi_provision_test PRIVATE digiradio_core)
|
target_link_libraries(wifi_provision_test PRIVATE digiradio_core)
|
||||||
add_test(NAME wifi_provision_test COMMAND wifi_provision_test)
|
add_test(NAME wifi_provision_test COMMAND wifi_provision_test)
|
||||||
|
|
||||||
|
add_executable(embedded_blob_reader_test embedded_blob_reader_test.cpp)
|
||||||
|
target_link_libraries(embedded_blob_reader_test PRIVATE digiradio_core)
|
||||||
|
add_test(NAME embedded_blob_reader_test COMMAND embedded_blob_reader_test)
|
||||||
|
|
||||||
|
add_executable(tuner_json_test tuner_json_test.cpp)
|
||||||
|
target_link_libraries(tuner_json_test PRIVATE digiradio_core)
|
||||||
|
add_test(NAME tuner_json_test COMMAND tuner_json_test)
|
||||||
|
|
||||||
|
add_executable(frequency_khz_test frequency_khz_test.cpp)
|
||||||
|
target_link_libraries(frequency_khz_test PRIVATE digiradio_core)
|
||||||
|
add_test(NAME frequency_khz_test COMMAND frequency_khz_test)
|
||||||
|
|||||||
@@ -0,0 +1,79 @@
|
|||||||
|
/**
|
||||||
|
* @file embedded_blob_reader_test.cpp
|
||||||
|
* @brief Host tests for EmbeddedBlobReader streaming reads.
|
||||||
|
*
|
||||||
|
* DigiRadio firmware — https://github.com/manvalan/DigiRadio
|
||||||
|
*
|
||||||
|
* Copyright 2026 Michele Bigi
|
||||||
|
* SPDX-License-Identifier: Apache-2.0
|
||||||
|
*
|
||||||
|
* @author Michele Bigi
|
||||||
|
* @date 2026-07-06
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include "core/EmbeddedBlobReader.hpp"
|
||||||
|
|
||||||
|
#include <array>
|
||||||
|
#include <cstdlib>
|
||||||
|
#include <iostream>
|
||||||
|
|
||||||
|
namespace {
|
||||||
|
|
||||||
|
bool expectEqual(std::size_t actual, std::size_t expected, const char* label)
|
||||||
|
{
|
||||||
|
if (actual == expected) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
std::cerr << label << ": expected " << expected << ", got " << actual
|
||||||
|
<< '\n';
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief main — run EmbeddedBlobReader host tests.
|
||||||
|
*
|
||||||
|
* @dname main
|
||||||
|
* @return 0 on success, 1 on failure.
|
||||||
|
* @pubstate none
|
||||||
|
*
|
||||||
|
* @author Michele Bigi
|
||||||
|
* @date 2026-07-06
|
||||||
|
*/
|
||||||
|
int main()
|
||||||
|
{
|
||||||
|
const std::array<std::byte, 8> source = {
|
||||||
|
std::byte{0x01}, std::byte{0x02}, std::byte{0x03}, std::byte{0x04},
|
||||||
|
std::byte{0x05}, std::byte{0x06}, std::byte{0x07}, std::byte{0x08},
|
||||||
|
};
|
||||||
|
core::EmbeddedBlobReader reader(source.data(), source.size());
|
||||||
|
|
||||||
|
if (!expectEqual(reader.size(), source.size(), "size")) {
|
||||||
|
return EXIT_FAILURE;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::array<std::byte, 3> chunk = {};
|
||||||
|
if (!expectEqual(reader.read(0U, chunk), 3U, "first read length")) {
|
||||||
|
return EXIT_FAILURE;
|
||||||
|
}
|
||||||
|
if (chunk[0] != std::byte{0x01} || chunk[2] != std::byte{0x03}) {
|
||||||
|
std::cerr << "first read content mismatch\n";
|
||||||
|
return EXIT_FAILURE;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!expectEqual(reader.read(6U, chunk), 2U, "tail read length")) {
|
||||||
|
return EXIT_FAILURE;
|
||||||
|
}
|
||||||
|
if (chunk[0] != std::byte{0x07} || chunk[1] != std::byte{0x08}) {
|
||||||
|
std::cerr << "tail read content mismatch\n";
|
||||||
|
return EXIT_FAILURE;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!expectEqual(reader.read(source.size(), chunk), 0U, "past end")) {
|
||||||
|
return EXIT_FAILURE;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::cout << "embedded_blob_reader_test: ok\n";
|
||||||
|
return EXIT_SUCCESS;
|
||||||
|
}
|
||||||
@@ -0,0 +1,53 @@
|
|||||||
|
/**
|
||||||
|
* @file frequency_khz_test.cpp
|
||||||
|
* @brief Host tests for FrequencyKHz validation.
|
||||||
|
*
|
||||||
|
* DigiRadio firmware — https://github.com/manvalan/DigiRadio
|
||||||
|
*
|
||||||
|
* Copyright 2026 Michele Bigi
|
||||||
|
* SPDX-License-Identifier: Apache-2.0
|
||||||
|
*
|
||||||
|
* @author Michele Bigi
|
||||||
|
* @date 2026-07-06
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include "core/FrequencyKHz.hpp"
|
||||||
|
#include "core/ParseError.hpp"
|
||||||
|
|
||||||
|
#include <cstdlib>
|
||||||
|
#include <iostream>
|
||||||
|
|
||||||
|
namespace {
|
||||||
|
|
||||||
|
[[nodiscard]] int runFrequencyAcceptTest()
|
||||||
|
{
|
||||||
|
const auto parsed = core::FrequencyKHz::tryFromKhz(101500U);
|
||||||
|
if (!parsed || parsed->value() != 101500U) {
|
||||||
|
std::cerr << "expected 101500 kHz accepted\n";
|
||||||
|
return EXIT_FAILURE;
|
||||||
|
}
|
||||||
|
return EXIT_SUCCESS;
|
||||||
|
}
|
||||||
|
|
||||||
|
[[nodiscard]] int runFrequencyRejectTest()
|
||||||
|
{
|
||||||
|
const auto parsed = core::FrequencyKHz::tryFromKhz(50000U);
|
||||||
|
if (parsed || parsed.error() != core::ParseError::MissingField) {
|
||||||
|
std::cerr << "expected out-of-band frequency rejection\n";
|
||||||
|
return EXIT_FAILURE;
|
||||||
|
}
|
||||||
|
return EXIT_SUCCESS;
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace
|
||||||
|
|
||||||
|
int main()
|
||||||
|
{
|
||||||
|
if (runFrequencyAcceptTest() != EXIT_SUCCESS) {
|
||||||
|
return EXIT_FAILURE;
|
||||||
|
}
|
||||||
|
if (runFrequencyRejectTest() != EXIT_SUCCESS) {
|
||||||
|
return EXIT_FAILURE;
|
||||||
|
}
|
||||||
|
return EXIT_SUCCESS;
|
||||||
|
}
|
||||||
@@ -0,0 +1,156 @@
|
|||||||
|
/**
|
||||||
|
* @file tuner_json_test.cpp
|
||||||
|
* @brief Host tests for tuner JSON parse/serialise.
|
||||||
|
*
|
||||||
|
* DigiRadio firmware — https://github.com/manvalan/DigiRadio
|
||||||
|
*
|
||||||
|
* Copyright 2026 Michele Bigi
|
||||||
|
* SPDX-License-Identifier: Apache-2.0
|
||||||
|
*
|
||||||
|
* @author Michele Bigi
|
||||||
|
* @date 2026-07-06
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include "core/FrequencyKHz.hpp"
|
||||||
|
#include "core/ParseError.hpp"
|
||||||
|
#include "core/TunerBand.hpp"
|
||||||
|
#include "core/TunerJson.hpp"
|
||||||
|
#include "core/TunerStatus.hpp"
|
||||||
|
|
||||||
|
#include <cstdlib>
|
||||||
|
#include <iostream>
|
||||||
|
#include <string>
|
||||||
|
|
||||||
|
namespace {
|
||||||
|
|
||||||
|
[[nodiscard]] bool expectEqual(const std::string& actual,
|
||||||
|
const std::string& expected)
|
||||||
|
{
|
||||||
|
if (actual == expected) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
std::cerr << "expected: " << expected << "\nactual: " << actual << '\n';
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
[[nodiscard]] int runTunerTuneParseDabTest()
|
||||||
|
{
|
||||||
|
const auto parsed =
|
||||||
|
core::parseTunerTuneJson(R"({"band":"dab","freq_index":12})");
|
||||||
|
if (!parsed || parsed->band != core::TunerBand::Dab
|
||||||
|
|| parsed->dabFreqIndex != 12U) {
|
||||||
|
std::cerr << "DAB tune parse failed\n";
|
||||||
|
return EXIT_FAILURE;
|
||||||
|
}
|
||||||
|
return EXIT_SUCCESS;
|
||||||
|
}
|
||||||
|
|
||||||
|
[[nodiscard]] int runTunerTuneParseFmTest()
|
||||||
|
{
|
||||||
|
const auto parsed =
|
||||||
|
core::parseTunerTuneJson(R"({"band":"fm","frequency_khz":101500})");
|
||||||
|
if (!parsed || parsed->band != core::TunerBand::Fm
|
||||||
|
|| !parsed->fmFrequency
|
||||||
|
|| parsed->fmFrequency->value() != 101500U) {
|
||||||
|
std::cerr << "FM tune parse failed\n";
|
||||||
|
return EXIT_FAILURE;
|
||||||
|
}
|
||||||
|
return EXIT_SUCCESS;
|
||||||
|
}
|
||||||
|
|
||||||
|
[[nodiscard]] int runTunerTuneRejectTest()
|
||||||
|
{
|
||||||
|
const auto parsed =
|
||||||
|
core::parseTunerTuneJson(R"({"band":"fm","frequency_khz":50000})");
|
||||||
|
if (parsed) {
|
||||||
|
std::cerr << "expected FM frequency rejection\n";
|
||||||
|
return EXIT_FAILURE;
|
||||||
|
}
|
||||||
|
return EXIT_SUCCESS;
|
||||||
|
}
|
||||||
|
|
||||||
|
[[nodiscard]] int runTunerPlayParseTest()
|
||||||
|
{
|
||||||
|
const auto parsed =
|
||||||
|
core::parseTunerPlayJson(R"({"service_id":42,"component_id":7})");
|
||||||
|
if (!parsed || parsed->serviceId != 42U || parsed->componentId != 7U) {
|
||||||
|
std::cerr << "play parse failed\n";
|
||||||
|
return EXIT_FAILURE;
|
||||||
|
}
|
||||||
|
return EXIT_SUCCESS;
|
||||||
|
}
|
||||||
|
|
||||||
|
[[nodiscard]] int runTunerStatusSerialiseTest()
|
||||||
|
{
|
||||||
|
core::TunerStatus status = {};
|
||||||
|
status.booted = true;
|
||||||
|
status.band = core::TunerBand::Fm;
|
||||||
|
status.locked = true;
|
||||||
|
status.volume = 40;
|
||||||
|
status.fmFrequency = *core::FrequencyKHz::tryFromKhz(101500U);
|
||||||
|
status.fmRssiDbuV = -20;
|
||||||
|
status.fmSnrDb = 25;
|
||||||
|
status.fmStereo = true;
|
||||||
|
|
||||||
|
const std::string json = core::serializeTunerStatusJson(status);
|
||||||
|
if (json.find("\"booted\":true") == std::string::npos
|
||||||
|
|| json.find("\"band\":\"fm\"") == std::string::npos
|
||||||
|
|| json.find("\"frequency_khz\":101500") == std::string::npos) {
|
||||||
|
std::cerr << "status serialise missing fields: " << json << '\n';
|
||||||
|
return EXIT_FAILURE;
|
||||||
|
}
|
||||||
|
return EXIT_SUCCESS;
|
||||||
|
}
|
||||||
|
|
||||||
|
[[nodiscard]] int runTunerServicesSerialiseTest()
|
||||||
|
{
|
||||||
|
core::TunerServiceEntry entry = {};
|
||||||
|
entry.serviceId = 1U;
|
||||||
|
entry.componentId = 2U;
|
||||||
|
entry.label = {'R', 'A', 'I', '\0'};
|
||||||
|
|
||||||
|
const std::string json =
|
||||||
|
core::serializeTunerServicesJson({entry});
|
||||||
|
if (!expectEqual(json,
|
||||||
|
R"({"services":[{"service_id":1,"component_id":2,"label":"RAI"}]})")) {
|
||||||
|
return EXIT_FAILURE;
|
||||||
|
}
|
||||||
|
return EXIT_SUCCESS;
|
||||||
|
}
|
||||||
|
|
||||||
|
[[nodiscard]] int runTunerErrorSerialiseTest()
|
||||||
|
{
|
||||||
|
if (!expectEqual(core::serializeTunerErrorJson("not_booted"),
|
||||||
|
R"({"status":"error","reason":"not_booted"})")) {
|
||||||
|
return EXIT_FAILURE;
|
||||||
|
}
|
||||||
|
return EXIT_SUCCESS;
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace
|
||||||
|
|
||||||
|
int main()
|
||||||
|
{
|
||||||
|
if (runTunerTuneParseDabTest() != EXIT_SUCCESS) {
|
||||||
|
return EXIT_FAILURE;
|
||||||
|
}
|
||||||
|
if (runTunerTuneParseFmTest() != EXIT_SUCCESS) {
|
||||||
|
return EXIT_FAILURE;
|
||||||
|
}
|
||||||
|
if (runTunerTuneRejectTest() != EXIT_SUCCESS) {
|
||||||
|
return EXIT_FAILURE;
|
||||||
|
}
|
||||||
|
if (runTunerPlayParseTest() != EXIT_SUCCESS) {
|
||||||
|
return EXIT_FAILURE;
|
||||||
|
}
|
||||||
|
if (runTunerStatusSerialiseTest() != EXIT_SUCCESS) {
|
||||||
|
return EXIT_FAILURE;
|
||||||
|
}
|
||||||
|
if (runTunerServicesSerialiseTest() != EXIT_SUCCESS) {
|
||||||
|
return EXIT_FAILURE;
|
||||||
|
}
|
||||||
|
if (runTunerErrorSerialiseTest() != EXIT_SUCCESS) {
|
||||||
|
return EXIT_FAILURE;
|
||||||
|
}
|
||||||
|
return EXIT_SUCCESS;
|
||||||
|
}
|
||||||
@@ -1,6 +1,14 @@
|
|||||||
|
set(ADAU_FW_DIR "${CMAKE_CURRENT_LIST_DIR}/../../../Firmware/ADAU1701-Firmware")
|
||||||
|
|
||||||
idf_component_register(
|
idf_component_register(
|
||||||
SRCS "src/component_stub.cpp"
|
SRCS
|
||||||
INCLUDE_DIRS "include"
|
"src/Adau1701Driver.cpp"
|
||||||
|
"src/SigmaStudioFW.c"
|
||||||
|
"src/adau1701_program.c"
|
||||||
|
INCLUDE_DIRS
|
||||||
|
"include"
|
||||||
|
"${ADAU_FW_DIR}"
|
||||||
|
REQUIRES driver esp_driver_gpio esp_driver_i2c
|
||||||
)
|
)
|
||||||
|
|
||||||
target_compile_features(${COMPONENT_LIB} PUBLIC cxx_std_23)
|
target_compile_features(${COMPONENT_LIB} PUBLIC cxx_std_23)
|
||||||
|
|||||||
@@ -0,0 +1,112 @@
|
|||||||
|
/**
|
||||||
|
* @file Adau1701Driver.hpp
|
||||||
|
* @brief ADAU1701 SigmaDSP driver — RAM boot on every power-up.
|
||||||
|
*
|
||||||
|
* DigiRadio firmware — https://github.com/manvalan/DigiRadio
|
||||||
|
*
|
||||||
|
* Copyright 2026 Michele Bigi
|
||||||
|
* SPDX-License-Identifier: Apache-2.0
|
||||||
|
*
|
||||||
|
* @author Michele Bigi
|
||||||
|
* @date 2026-07-06
|
||||||
|
*/
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "adau1701/Adau1701Error.hpp"
|
||||||
|
|
||||||
|
#include <expected>
|
||||||
|
|
||||||
|
namespace adau1701 {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Adau1701Pins — board GPIO/I2C identifiers for the DSP.
|
||||||
|
*
|
||||||
|
* @dname Adau1701Pins
|
||||||
|
* @return n/a (type)
|
||||||
|
* @pubstate Immutable wiring snapshot from board_pins.hpp.
|
||||||
|
*
|
||||||
|
* @author Michele Bigi
|
||||||
|
* @date 2026-07-06
|
||||||
|
*/
|
||||||
|
struct Adau1701Pins {
|
||||||
|
int i2cSda; ///< I2C SDA GPIO.
|
||||||
|
int i2cScl; ///< I2C SCL GPIO.
|
||||||
|
int resetGpio; ///< DSP RESET# GPIO (active low).
|
||||||
|
int i2cAddr7; ///< 7-bit I2C address of the ADAU1701.
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Adau1701Driver — owns I2C + reset and loads SigmaStudio RAM.
|
||||||
|
*
|
||||||
|
* @dname Adau1701Driver
|
||||||
|
* @param pins Board wiring for I2C and RESET#.
|
||||||
|
* @return n/a (type)
|
||||||
|
* @pubstate Owns I2C bus/device handles (RAII). booted_ true after a
|
||||||
|
* successful default_download replay.
|
||||||
|
*
|
||||||
|
* Writes the SigmaStudio export from Firmware/ADAU1701-Firmware on every
|
||||||
|
* boot (no EEPROM self-boot on DigiRadio).
|
||||||
|
*
|
||||||
|
* @author Michele Bigi
|
||||||
|
* @date 2026-07-06
|
||||||
|
*/
|
||||||
|
class Adau1701Driver {
|
||||||
|
public:
|
||||||
|
/**
|
||||||
|
* @brief Adau1701Driver — construct with board pin map.
|
||||||
|
*
|
||||||
|
* @dname Adau1701Driver
|
||||||
|
* @param pins SDA/SCL/reset/address configuration.
|
||||||
|
* @pubstate stores pins_; not booted until boot().
|
||||||
|
*
|
||||||
|
* @author Michele Bigi
|
||||||
|
* @date 2026-07-06
|
||||||
|
*/
|
||||||
|
explicit Adau1701Driver(Adau1701Pins pins);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief ~Adau1701Driver — release I2C resources.
|
||||||
|
*
|
||||||
|
* @dname ~Adau1701Driver
|
||||||
|
* @pubstate deletes bus/device handles when created.
|
||||||
|
*
|
||||||
|
* @author Michele Bigi
|
||||||
|
* @date 2026-07-06
|
||||||
|
*/
|
||||||
|
~Adau1701Driver();
|
||||||
|
|
||||||
|
Adau1701Driver(const Adau1701Driver&) = delete;
|
||||||
|
Adau1701Driver& operator=(const Adau1701Driver&) = delete;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief boot — reset the DSP and replay the SigmaStudio download.
|
||||||
|
*
|
||||||
|
* @dname boot
|
||||||
|
* @return Ok on success, or Adau1701Error.
|
||||||
|
* @pubstate writes booted_ on success; uses embedded program data.
|
||||||
|
*
|
||||||
|
* @author Michele Bigi
|
||||||
|
* @date 2026-07-06
|
||||||
|
*/
|
||||||
|
[[nodiscard]] std::expected<void, Adau1701Error> boot();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief isBooted — query whether RAM download succeeded.
|
||||||
|
*
|
||||||
|
* @dname isBooted
|
||||||
|
* @return true after a successful boot().
|
||||||
|
* @pubstate reads booted_.
|
||||||
|
*
|
||||||
|
* @author Michele Bigi
|
||||||
|
* @date 2026-07-06
|
||||||
|
*/
|
||||||
|
[[nodiscard]] bool isBooted() const noexcept;
|
||||||
|
|
||||||
|
private:
|
||||||
|
Adau1701Pins pins_;
|
||||||
|
bool booted_;
|
||||||
|
void* i2cBus_;
|
||||||
|
void* i2cDev_;
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace adau1701
|
||||||
@@ -0,0 +1,33 @@
|
|||||||
|
/**
|
||||||
|
* @file Adau1701Error.hpp
|
||||||
|
* @brief Typed errors for ADAU1701 driver operations.
|
||||||
|
*
|
||||||
|
* DigiRadio firmware — https://github.com/manvalan/DigiRadio
|
||||||
|
*
|
||||||
|
* Copyright 2026 Michele Bigi
|
||||||
|
* SPDX-License-Identifier: Apache-2.0
|
||||||
|
*
|
||||||
|
* @author Michele Bigi
|
||||||
|
* @date 2026-07-06
|
||||||
|
*/
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
namespace adau1701 {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Adau1701Error — failure causes for ADAU1701 bring-up.
|
||||||
|
*
|
||||||
|
* @dname Adau1701Error
|
||||||
|
* @return n/a (type)
|
||||||
|
* @pubstate n/a
|
||||||
|
*
|
||||||
|
* @author Michele Bigi
|
||||||
|
* @date 2026-07-06
|
||||||
|
*/
|
||||||
|
enum class Adau1701Error {
|
||||||
|
I2cInitFailed,
|
||||||
|
ResetFailed,
|
||||||
|
DownloadFailed,
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace adau1701
|
||||||
@@ -0,0 +1,118 @@
|
|||||||
|
/**
|
||||||
|
* @file Adau1701Driver.cpp
|
||||||
|
* @brief Adau1701Driver implementation.
|
||||||
|
*
|
||||||
|
* DigiRadio firmware — https://github.com/manvalan/DigiRadio
|
||||||
|
*
|
||||||
|
* Copyright 2026 Michele Bigi
|
||||||
|
* SPDX-License-Identifier: Apache-2.0
|
||||||
|
*
|
||||||
|
* @author Michele Bigi
|
||||||
|
* @date 2026-07-06
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include "adau1701/Adau1701Driver.hpp"
|
||||||
|
|
||||||
|
#include "SigmaStudioFW.h"
|
||||||
|
|
||||||
|
#include "driver/gpio.h"
|
||||||
|
#include "driver/i2c_master.h"
|
||||||
|
#include "esp_log.h"
|
||||||
|
#include "freertos/FreeRTOS.h"
|
||||||
|
#include "freertos/task.h"
|
||||||
|
|
||||||
|
extern "C" {
|
||||||
|
|
||||||
|
/** @brief SigmaStudio default program download (generated C, not part of the C++ API). */
|
||||||
|
void adau1701_run_default_download(void);
|
||||||
|
|
||||||
|
} // extern "C"
|
||||||
|
|
||||||
|
namespace adau1701 {
|
||||||
|
|
||||||
|
namespace {
|
||||||
|
constexpr char kTag[] = "Adau1701";
|
||||||
|
constexpr int kI2cPort = 0;
|
||||||
|
} // namespace
|
||||||
|
|
||||||
|
Adau1701Driver::Adau1701Driver(Adau1701Pins pins)
|
||||||
|
: pins_(pins)
|
||||||
|
, booted_(false)
|
||||||
|
, i2cBus_(nullptr)
|
||||||
|
, i2cDev_(nullptr)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
Adau1701Driver::~Adau1701Driver()
|
||||||
|
{
|
||||||
|
auto* dev = static_cast<i2c_master_dev_handle_t>(i2cDev_);
|
||||||
|
auto* bus = static_cast<i2c_master_bus_handle_t>(i2cBus_);
|
||||||
|
if (dev != nullptr) {
|
||||||
|
i2c_master_bus_rm_device(dev);
|
||||||
|
}
|
||||||
|
if (bus != nullptr) {
|
||||||
|
i2c_del_master_bus(bus);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
std::expected<void, Adau1701Error> Adau1701Driver::boot()
|
||||||
|
{
|
||||||
|
if (booted_) {
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
|
||||||
|
gpio_config_t resetCfg = {};
|
||||||
|
resetCfg.pin_bit_mask = 1ULL << pins_.resetGpio;
|
||||||
|
resetCfg.mode = GPIO_MODE_OUTPUT;
|
||||||
|
if (gpio_config(&resetCfg) != ESP_OK) {
|
||||||
|
return std::unexpected(Adau1701Error::ResetFailed);
|
||||||
|
}
|
||||||
|
|
||||||
|
gpio_set_level(static_cast<gpio_num_t>(pins_.resetGpio), 0);
|
||||||
|
vTaskDelay(pdMS_TO_TICKS(10));
|
||||||
|
gpio_set_level(static_cast<gpio_num_t>(pins_.resetGpio), 1);
|
||||||
|
vTaskDelay(pdMS_TO_TICKS(10));
|
||||||
|
|
||||||
|
i2c_master_bus_config_t busCfg = {};
|
||||||
|
busCfg.i2c_port = static_cast<i2c_port_num_t>(kI2cPort);
|
||||||
|
busCfg.sda_io_num = static_cast<gpio_num_t>(pins_.i2cSda);
|
||||||
|
busCfg.scl_io_num = static_cast<gpio_num_t>(pins_.i2cScl);
|
||||||
|
busCfg.clk_source = I2C_CLK_SRC_DEFAULT;
|
||||||
|
busCfg.glitch_ignore_cnt = 7;
|
||||||
|
busCfg.flags.enable_internal_pullup = true;
|
||||||
|
|
||||||
|
i2c_master_bus_handle_t bus = nullptr;
|
||||||
|
if (i2c_new_master_bus(&busCfg, &bus) != ESP_OK) {
|
||||||
|
ESP_LOGE(kTag, "i2c_new_master_bus failed");
|
||||||
|
return std::unexpected(Adau1701Error::I2cInitFailed);
|
||||||
|
}
|
||||||
|
i2cBus_ = bus;
|
||||||
|
|
||||||
|
i2c_device_config_t devCfg = {};
|
||||||
|
devCfg.dev_addr_length = I2C_ADDR_BIT_LEN_7;
|
||||||
|
devCfg.device_address = static_cast<uint16_t>(pins_.i2cAddr7);
|
||||||
|
devCfg.scl_speed_hz = 100000;
|
||||||
|
|
||||||
|
i2c_master_dev_handle_t dev = nullptr;
|
||||||
|
if (i2c_master_bus_add_device(bus, &devCfg, &dev) != ESP_OK) {
|
||||||
|
ESP_LOGE(kTag, "i2c_master_bus_add_device failed");
|
||||||
|
return std::unexpected(Adau1701Error::I2cInitFailed);
|
||||||
|
}
|
||||||
|
i2cDev_ = dev;
|
||||||
|
|
||||||
|
sigma_studio_bind_i2c(kI2cPort, static_cast<unsigned char>(pins_.i2cAddr7));
|
||||||
|
sigma_studio_set_device(dev);
|
||||||
|
|
||||||
|
adau1701_run_default_download();
|
||||||
|
|
||||||
|
booted_ = true;
|
||||||
|
ESP_LOGI(kTag, "SigmaStudio program loaded");
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
|
||||||
|
bool Adau1701Driver::isBooted() const noexcept
|
||||||
|
{
|
||||||
|
return booted_;
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace adau1701
|
||||||
@@ -0,0 +1,60 @@
|
|||||||
|
/**
|
||||||
|
* @file SigmaStudioFW.c
|
||||||
|
* @brief SigmaStudio SIGMA_WRITE_REGISTER_BLOCK for ESP-IDF I2C.
|
||||||
|
*
|
||||||
|
* DigiRadio firmware — https://github.com/manvalan/DigiRadio
|
||||||
|
*
|
||||||
|
* Copyright 2026 Michele Bigi
|
||||||
|
* SPDX-License-Identifier: Apache-2.0
|
||||||
|
*
|
||||||
|
* @author Michele Bigi
|
||||||
|
* @date 2026-07-06
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include "SigmaStudioFW.h"
|
||||||
|
|
||||||
|
#include "driver/i2c_master.h"
|
||||||
|
|
||||||
|
#include <string.h>
|
||||||
|
|
||||||
|
static i2c_master_dev_handle_t s_dev = NULL;
|
||||||
|
|
||||||
|
void sigma_studio_bind_i2c(int port, unsigned char addr7)
|
||||||
|
{
|
||||||
|
(void)port;
|
||||||
|
(void)addr7;
|
||||||
|
}
|
||||||
|
|
||||||
|
void sigma_studio_set_device(void* i2cDevHandle)
|
||||||
|
{
|
||||||
|
s_dev = (i2c_master_dev_handle_t)i2cDevHandle;
|
||||||
|
}
|
||||||
|
|
||||||
|
void SIGMA_WRITE_REGISTER_BLOCK(unsigned char devAddress,
|
||||||
|
unsigned int address,
|
||||||
|
unsigned char length,
|
||||||
|
ADI_REG_TYPE* pData)
|
||||||
|
{
|
||||||
|
(void)devAddress;
|
||||||
|
if (s_dev == NULL || pData == NULL || length == 0U) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
enum { kChunk = 64U };
|
||||||
|
unsigned int addr = address;
|
||||||
|
unsigned char remaining = length;
|
||||||
|
ADI_REG_TYPE* cursor = pData;
|
||||||
|
|
||||||
|
while (remaining > 0U) {
|
||||||
|
const unsigned char chunk =
|
||||||
|
remaining > kChunk ? kChunk : remaining;
|
||||||
|
unsigned char buf[2U + 64U];
|
||||||
|
buf[0] = (unsigned char)((addr >> 8) & 0xFFU);
|
||||||
|
buf[1] = (unsigned char)(addr & 0xFFU);
|
||||||
|
memcpy(buf + 2U, cursor, chunk);
|
||||||
|
i2c_master_transmit(s_dev, buf, (size_t)(2U + chunk), 1000);
|
||||||
|
addr += chunk;
|
||||||
|
cursor += chunk;
|
||||||
|
remaining = (unsigned char)(remaining - chunk);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,20 @@
|
|||||||
|
/**
|
||||||
|
* @file adau1701_program.c
|
||||||
|
* @brief SigmaStudio default download for DigiRadio ADAU1701.
|
||||||
|
*
|
||||||
|
* DigiRadio firmware — https://github.com/manvalan/DigiRadio
|
||||||
|
*
|
||||||
|
* Copyright 2026 Michele Bigi
|
||||||
|
* SPDX-License-Identifier: Apache-2.0
|
||||||
|
*
|
||||||
|
* @author Michele Bigi
|
||||||
|
* @date 2026-07-06
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include "SigmaStudioFW.h"
|
||||||
|
#include "DigiRadio_IC_1.h"
|
||||||
|
|
||||||
|
void adau1701_run_default_download(void)
|
||||||
|
{
|
||||||
|
default_download_IC_1();
|
||||||
|
}
|
||||||
@@ -1,33 +0,0 @@
|
|||||||
/**
|
|
||||||
* @file component_stub.cpp
|
|
||||||
* @brief ADAU1701 driver component placeholder (Slice 5).
|
|
||||||
*
|
|
||||||
* DigiRadio firmware — https://github.com/manvalan/DigiRadio
|
|
||||||
*
|
|
||||||
* Copyright 2026 Michele Bigi
|
|
||||||
* SPDX-License-Identifier: Apache-2.0
|
|
||||||
*
|
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
||||||
* you may not use this file except in compliance with the License.
|
|
||||||
* You may obtain a copy of the License at
|
|
||||||
* http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
*
|
|
||||||
* @author Michele Bigi
|
|
||||||
* @date 2026-07-06
|
|
||||||
*/
|
|
||||||
|
|
||||||
namespace adau1701::detail {
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief adau1701ComponentLinked — ensures the driver component links.
|
|
||||||
*
|
|
||||||
* @dname adau1701ComponentLinked
|
|
||||||
* @return n/a (type)
|
|
||||||
* @pubstate n/a
|
|
||||||
*
|
|
||||||
* @author Michele Bigi
|
|
||||||
* @date 2026-07-06
|
|
||||||
*/
|
|
||||||
void adau1701ComponentLinked() noexcept {}
|
|
||||||
|
|
||||||
} // namespace adau1701::detail
|
|
||||||
@@ -1,6 +1,23 @@
|
|||||||
|
set(SI4684_FW_DIR "${CMAKE_CURRENT_LIST_DIR}/../../../Firmware/Si4684-Firmware")
|
||||||
|
|
||||||
|
if(NOT EXISTS "${SI4684_FW_DIR}/fm_firmware.bin")
|
||||||
|
message(FATAL_ERROR
|
||||||
|
"Missing ${SI4684_FW_DIR}/fm_firmware.bin — run:\n"
|
||||||
|
" python3 tools/fetch_si4684_firmware.py --from-ugreen-radio-cli <Files_v16.zip>\n"
|
||||||
|
" python3 tools/fetch_si4684_firmware.py --si46xx-dir <si46xx_firmware>")
|
||||||
|
endif()
|
||||||
|
|
||||||
idf_component_register(
|
idf_component_register(
|
||||||
SRCS "src/component_stub.cpp"
|
SRCS
|
||||||
|
"src/Si4684Driver.cpp"
|
||||||
|
"src/Si4684EmbeddedImages.cpp"
|
||||||
|
"src/Si4684Tuner.cpp"
|
||||||
INCLUDE_DIRS "include"
|
INCLUDE_DIRS "include"
|
||||||
|
REQUIRES core driver esp_driver_gpio esp_driver_spi
|
||||||
|
EMBED_FILES
|
||||||
|
"${SI4684_FW_DIR}/rom_patch_016.bin"
|
||||||
|
"${SI4684_FW_DIR}/dab_firmware.bin"
|
||||||
|
"${SI4684_FW_DIR}/fm_firmware.bin"
|
||||||
)
|
)
|
||||||
|
|
||||||
target_compile_features(${COMPONENT_LIB} PUBLIC cxx_std_23)
|
target_compile_features(${COMPONENT_LIB} PUBLIC cxx_std_23)
|
||||||
|
|||||||
@@ -0,0 +1,32 @@
|
|||||||
|
/**
|
||||||
|
* @file Si4684Band.hpp
|
||||||
|
* @brief Tuner band selection for Si4684 firmware images.
|
||||||
|
*
|
||||||
|
* DigiRadio firmware — https://github.com/manvalan/DigiRadio
|
||||||
|
*
|
||||||
|
* Copyright 2026 Michele Bigi
|
||||||
|
* SPDX-License-Identifier: Apache-2.0
|
||||||
|
*
|
||||||
|
* @author Michele Bigi
|
||||||
|
* @date 2026-07-06
|
||||||
|
*/
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
namespace si4684 {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Si4684Band — application image loaded after ROM patch.
|
||||||
|
*
|
||||||
|
* @dname Si4684Band
|
||||||
|
* @return n/a (type)
|
||||||
|
* @pubstate n/a
|
||||||
|
*
|
||||||
|
* @author Michele Bigi
|
||||||
|
* @date 2026-07-06
|
||||||
|
*/
|
||||||
|
enum class Si4684Band {
|
||||||
|
Dab,
|
||||||
|
Fm,
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace si4684
|
||||||
@@ -0,0 +1,370 @@
|
|||||||
|
/**
|
||||||
|
* @file Si4684Driver.hpp
|
||||||
|
* @brief Si4684 DAB+/FM tuner — boot, tuning, status, and service control.
|
||||||
|
*
|
||||||
|
* DigiRadio firmware — https://github.com/manvalan/DigiRadio
|
||||||
|
*
|
||||||
|
* Copyright 2026 Michele Bigi
|
||||||
|
* SPDX-License-Identifier: Apache-2.0
|
||||||
|
*
|
||||||
|
* @author Michele Bigi
|
||||||
|
* @date 2026-07-06
|
||||||
|
*/
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "core/FrequencyKHz.hpp"
|
||||||
|
#include "core/IFirmwareBlobReader.hpp"
|
||||||
|
#include "core/SeekDirection.hpp"
|
||||||
|
#include "si4684/Si4684Band.hpp"
|
||||||
|
#include "si4684/Si4684Error.hpp"
|
||||||
|
#include "si4684/Si4684Types.hpp"
|
||||||
|
|
||||||
|
#include <cstdint>
|
||||||
|
#include <expected>
|
||||||
|
#include <span>
|
||||||
|
#include <vector>
|
||||||
|
|
||||||
|
namespace si4684 {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Si4684Pins — board GPIO/SPI identifiers for the tuner.
|
||||||
|
*
|
||||||
|
* @dname Si4684Pins
|
||||||
|
* @return n/a (type)
|
||||||
|
* @pubstate Immutable wiring snapshot from board_pins.hpp.
|
||||||
|
*
|
||||||
|
* @author Michele Bigi
|
||||||
|
* @date 2026-07-06
|
||||||
|
*/
|
||||||
|
struct Si4684Pins {
|
||||||
|
int spiHost; ///< ESP32 SPI host peripheral index.
|
||||||
|
int csGpio; ///< Chip-select GPIO.
|
||||||
|
int misoGpio; ///< MISO GPIO.
|
||||||
|
int mosiGpio; ///< MOSI GPIO.
|
||||||
|
int sclkGpio; ///< SCLK GPIO.
|
||||||
|
int rstbGpio; ///< RESET# GPIO (active low).
|
||||||
|
int intbGpio; ///< INTB GPIO (optional status interrupt).
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Si4684Driver — full Si4684 control over SPI (AN649 / PE5PVB).
|
||||||
|
*
|
||||||
|
* @dname Si4684Driver
|
||||||
|
* @return n/a (type)
|
||||||
|
* @pubstate Owns the SPI link and boot state. Register-level command bytes
|
||||||
|
* stay private; callers use intent-level methods only.
|
||||||
|
*
|
||||||
|
* Implements the documented boot sequence plus band-specific tuning, property
|
||||||
|
* access, RSQ/DIGRAD reads, and DAB service selection.
|
||||||
|
*
|
||||||
|
* @author Michele Bigi
|
||||||
|
* @date 2026-07-06
|
||||||
|
*/
|
||||||
|
class Si4684Driver {
|
||||||
|
public:
|
||||||
|
/**
|
||||||
|
* @brief Si4684Driver — construct with board pins and firmware blobs.
|
||||||
|
*
|
||||||
|
* @dname Si4684Driver
|
||||||
|
* @param pins Board SPI and GPIO wiring.
|
||||||
|
* @param patch ROM patch blob reader for HOST_LOAD.
|
||||||
|
* @param dabImage DAB application image reader.
|
||||||
|
* @param fmImage FM application image reader.
|
||||||
|
* @pubstate stores pin map and blob references; not booted until boot().
|
||||||
|
*
|
||||||
|
* @author Michele Bigi
|
||||||
|
* @date 2026-07-06
|
||||||
|
*/
|
||||||
|
Si4684Driver(Si4684Pins pins,
|
||||||
|
const core::IFirmwareBlobReader& patch,
|
||||||
|
const core::IFirmwareBlobReader& dabImage,
|
||||||
|
const core::IFirmwareBlobReader& fmImage);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief ~Si4684Driver — release SPI resources.
|
||||||
|
*
|
||||||
|
* @dname ~Si4684Driver
|
||||||
|
* @pubstate removes SPI device and bus when active.
|
||||||
|
*
|
||||||
|
* @author Michele Bigi
|
||||||
|
* @date 2026-07-06
|
||||||
|
*/
|
||||||
|
~Si4684Driver();
|
||||||
|
|
||||||
|
Si4684Driver(const Si4684Driver&) = delete;
|
||||||
|
Si4684Driver& operator=(const Si4684Driver&) = delete;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief boot — cold-start: load patch, image, and configure I/O.
|
||||||
|
*
|
||||||
|
* @dname boot
|
||||||
|
* @param band DAB or FM application to load.
|
||||||
|
* @return Ok on success, or Si4684Error.
|
||||||
|
* @pubstate writes booted_ and loadedBand_ on success.
|
||||||
|
*
|
||||||
|
* @author Michele Bigi
|
||||||
|
* @date 2026-07-06
|
||||||
|
*/
|
||||||
|
[[nodiscard]] std::expected<void, Si4684Error> boot(Si4684Band band);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief isBooted — query whether boot completed successfully.
|
||||||
|
*
|
||||||
|
* @dname isBooted
|
||||||
|
* @return true after a successful boot().
|
||||||
|
* @pubstate reads booted_.
|
||||||
|
*
|
||||||
|
* @author Michele Bigi
|
||||||
|
* @date 2026-07-06
|
||||||
|
*/
|
||||||
|
[[nodiscard]] bool isBooted() const noexcept;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief loadedBand — read the active application band.
|
||||||
|
*
|
||||||
|
* @dname loadedBand
|
||||||
|
* @return Si4684Band loaded by the last successful boot().
|
||||||
|
* @pubstate reads loadedBand_.
|
||||||
|
*
|
||||||
|
* @author Michele Bigi
|
||||||
|
* @date 2026-07-06
|
||||||
|
*/
|
||||||
|
[[nodiscard]] Si4684Band loadedBand() const noexcept;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief getPartInfo — read chip identity and firmware revision.
|
||||||
|
*
|
||||||
|
* @dname getPartInfo
|
||||||
|
* @return Si4684PartInfo on success, or Si4684Error.
|
||||||
|
* @pubstate reads chip via GET_PART_INFO (AN649).
|
||||||
|
*
|
||||||
|
* @author Michele Bigi
|
||||||
|
* @date 2026-07-06
|
||||||
|
*/
|
||||||
|
[[nodiscard]] std::expected<Si4684PartInfo, Si4684Error> getPartInfo();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief getSysState — read the running application state.
|
||||||
|
*
|
||||||
|
* @dname getSysState
|
||||||
|
* @return Si4684SysState on success, or Si4684Error.
|
||||||
|
* @pubstate reads chip via GET_SYS_STATE.
|
||||||
|
*
|
||||||
|
* @author Michele Bigi
|
||||||
|
* @date 2026-07-06
|
||||||
|
*/
|
||||||
|
[[nodiscard]] std::expected<Si4684SysState, Si4684Error> getSysState();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief setProperty — write a Skyworks property (SET_PROPERTY 0x13).
|
||||||
|
*
|
||||||
|
* @dname setProperty
|
||||||
|
* @param propertyId 16-bit property address.
|
||||||
|
* @param value 16-bit property value.
|
||||||
|
* @return Ok on success, or Si4684Error.
|
||||||
|
* @pubstate writes property via SPI command.
|
||||||
|
*
|
||||||
|
* @author Michele Bigi
|
||||||
|
* @date 2026-07-06
|
||||||
|
*/
|
||||||
|
[[nodiscard]] std::expected<void, Si4684Error> setProperty(
|
||||||
|
std::uint16_t propertyId, std::uint16_t value);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief setVolume — set audio attenuator 0–63 (property 0x0300).
|
||||||
|
*
|
||||||
|
* @dname setVolume
|
||||||
|
* @param level Attenuator level 0–63.
|
||||||
|
* @return Ok on success, or Si4684Error.
|
||||||
|
* @pubstate writes AUDIO_VOLUME property.
|
||||||
|
*
|
||||||
|
* @author Michele Bigi
|
||||||
|
* @date 2026-07-06
|
||||||
|
*/
|
||||||
|
[[nodiscard]] std::expected<void, Si4684Error> setVolume(std::uint8_t level);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief tuneFm — tune FM to a validated centre frequency.
|
||||||
|
*
|
||||||
|
* @dname tuneFm
|
||||||
|
* @param frequency FM centre frequency in kHz.
|
||||||
|
* @return Ok on success, or Si4684Error::WrongBand / TuneFailed.
|
||||||
|
* @pubstate sends FM_TUNE_FREQ and waits for STC (AN649).
|
||||||
|
*
|
||||||
|
* @author Michele Bigi
|
||||||
|
* @date 2026-07-06
|
||||||
|
*/
|
||||||
|
[[nodiscard]] std::expected<void, Si4684Error> tuneFm(
|
||||||
|
core::FrequencyKHz frequency);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief seekFm — seek FM in the given direction.
|
||||||
|
*
|
||||||
|
* @dname seekFm
|
||||||
|
* @param direction Up or Down scan direction.
|
||||||
|
* @param wrap Band wrap behaviour for FM_SEEK_START.
|
||||||
|
* @return New centre frequency on success, or Si4684Error.
|
||||||
|
* @pubstate sends FM_SEEK_START and waits for STC.
|
||||||
|
*
|
||||||
|
* @author Michele Bigi
|
||||||
|
* @date 2026-07-06
|
||||||
|
*/
|
||||||
|
[[nodiscard]] std::expected<core::FrequencyKHz, Si4684Error> seekFm(
|
||||||
|
core::SeekDirection direction, SeekBandWrap wrap);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief readFmRsq — read FM signal quality metrics.
|
||||||
|
*
|
||||||
|
* @dname readFmRsq
|
||||||
|
* @return Si4684FmRsq on success, or Si4684Error.
|
||||||
|
* @pubstate reads FM_RSQ_STATUS response bytes.
|
||||||
|
*
|
||||||
|
* @author Michele Bigi
|
||||||
|
* @date 2026-07-06
|
||||||
|
*/
|
||||||
|
[[nodiscard]] std::expected<Si4684FmRsq, Si4684Error> readFmRsq();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief readFmRds — read a raw RDS group snapshot.
|
||||||
|
*
|
||||||
|
* @dname readFmRds
|
||||||
|
* @return Si4684FmRdsStatus on success, or Si4684Error.
|
||||||
|
* @pubstate reads FM_RDS_STATUS response bytes.
|
||||||
|
*
|
||||||
|
* @author Michele Bigi
|
||||||
|
* @date 2026-07-06
|
||||||
|
*/
|
||||||
|
[[nodiscard]] std::expected<Si4684FmRdsStatus, Si4684Error> readFmRds();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief installDefaultDabFrequencyPlan — load Band III frequency list.
|
||||||
|
*
|
||||||
|
* @dname installDefaultDabFrequencyPlan
|
||||||
|
* @return Ok on success, or Si4684Error.
|
||||||
|
* @pubstate sends DAB_SET_FREQ_LIST with kDefaultDabFrequencyKhz.
|
||||||
|
*
|
||||||
|
* @author Michele Bigi
|
||||||
|
* @date 2026-07-06
|
||||||
|
*/
|
||||||
|
[[nodiscard]] std::expected<void, Si4684Error> installDefaultDabFrequencyPlan();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief tuneDab — tune to a Band III ensemble index.
|
||||||
|
*
|
||||||
|
* @dname tuneDab
|
||||||
|
* @param freqIndex Ensemble index 0–37.
|
||||||
|
* @return Ok on success, or Si4684Error.
|
||||||
|
* @pubstate sends DAB_TUNE_FREQ and waits for STC.
|
||||||
|
*
|
||||||
|
* @author Michele Bigi
|
||||||
|
* @date 2026-07-06
|
||||||
|
*/
|
||||||
|
[[nodiscard]] std::expected<void, Si4684Error> tuneDab(std::uint8_t freqIndex);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief readDabDigRadStatus — read ensemble lock metrics.
|
||||||
|
*
|
||||||
|
* @dname readDabDigRadStatus
|
||||||
|
* @return Si4684DabDigRadStatus on success, or Si4684Error.
|
||||||
|
* @pubstate reads DAB_DIGRAD_STATUS response bytes.
|
||||||
|
*
|
||||||
|
* @author Michele Bigi
|
||||||
|
* @date 2026-07-06
|
||||||
|
*/
|
||||||
|
[[nodiscard]] std::expected<Si4684DabDigRadStatus, Si4684Error>
|
||||||
|
readDabDigRadStatus();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief readDabEventStatus — read DAB event flags.
|
||||||
|
*
|
||||||
|
* @dname readDabEventStatus
|
||||||
|
* @return Si4684DabEventStatus on success, or Si4684Error.
|
||||||
|
* @pubstate reads DAB_GET_EVENT_STATUS response bytes.
|
||||||
|
*
|
||||||
|
* @author Michele Bigi
|
||||||
|
* @date 2026-07-06
|
||||||
|
*/
|
||||||
|
[[nodiscard]] std::expected<Si4684DabEventStatus, Si4684Error>
|
||||||
|
readDabEventStatus();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief fetchDabServiceList — retrieve programmes for the ensemble.
|
||||||
|
*
|
||||||
|
* @dname fetchDabServiceList
|
||||||
|
* @return Service rows on success, or Si4684Error.
|
||||||
|
* @pubstate reads GET_DIGITAL_SERVICE_LIST chunks from the chip.
|
||||||
|
*
|
||||||
|
* @author Michele Bigi
|
||||||
|
* @date 2026-07-06
|
||||||
|
*/
|
||||||
|
[[nodiscard]] std::expected<std::vector<Si4684DabService>, Si4684Error>
|
||||||
|
fetchDabServiceList();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief startDabService — start DAB audio for a programme.
|
||||||
|
*
|
||||||
|
* @dname startDabService
|
||||||
|
* @param serviceId Selected service identifier.
|
||||||
|
* @param componentId Audio component within the service.
|
||||||
|
* @param type Digital service type (audio by default).
|
||||||
|
* @return Ok on success, or Si4684Error.
|
||||||
|
* @pubstate sends START_DIGITAL_SERVICE.
|
||||||
|
*
|
||||||
|
* @author Michele Bigi
|
||||||
|
* @date 2026-07-06
|
||||||
|
*/
|
||||||
|
[[nodiscard]] std::expected<void, Si4684Error> startDabService(
|
||||||
|
std::uint32_t serviceId,
|
||||||
|
std::uint32_t componentId,
|
||||||
|
Si4684DigitalServiceType type = Si4684DigitalServiceType::Audio);
|
||||||
|
|
||||||
|
private:
|
||||||
|
enum class Command : std::uint8_t {
|
||||||
|
PowerUp = 0x01,
|
||||||
|
HostLoad = 0x04,
|
||||||
|
LoadInit = 0x06,
|
||||||
|
BootCmd = 0x07,
|
||||||
|
GetPartInfo = 0x08,
|
||||||
|
GetSysState = 0x09,
|
||||||
|
GetFuncInfo = 0x12,
|
||||||
|
SetProperty = 0x13,
|
||||||
|
FmTuneFreq = 0x30,
|
||||||
|
FmSeekStart = 0x31,
|
||||||
|
FmRsqStatus = 0x32,
|
||||||
|
FmRdsStatus = 0x34,
|
||||||
|
GetDigitalServiceList = 0x80,
|
||||||
|
StartDigitalService = 0x81,
|
||||||
|
GetDigitalServiceData = 0x84,
|
||||||
|
DabTuneFreq = 0xB0,
|
||||||
|
DabDigRadStatus = 0xB2,
|
||||||
|
DabGetEventStatus = 0xB3,
|
||||||
|
DabSetFreqList = 0xB8,
|
||||||
|
};
|
||||||
|
|
||||||
|
[[nodiscard]] std::expected<void, Si4684Error> ensureBooted() const;
|
||||||
|
[[nodiscard]] std::expected<void, Si4684Error> ensureBand(
|
||||||
|
Si4684Band band) const;
|
||||||
|
[[nodiscard]] std::expected<void, Si4684Error> waitCts();
|
||||||
|
[[nodiscard]] std::expected<void, Si4684Error> waitStc();
|
||||||
|
[[nodiscard]] std::expected<void, Si4684Error> sendCommand(
|
||||||
|
std::span<const std::uint8_t> bytes);
|
||||||
|
[[nodiscard]] std::expected<void, Si4684Error> readRaw(
|
||||||
|
std::span<std::uint8_t> buffer);
|
||||||
|
[[nodiscard]] std::expected<void, Si4684Error> writeCommand(
|
||||||
|
Command cmd, const std::uint8_t* payload, std::size_t length);
|
||||||
|
[[nodiscard]] std::expected<void, Si4684Error> hostLoadBlob(
|
||||||
|
const core::IFirmwareBlobReader& blob, std::size_t chunkPayload);
|
||||||
|
[[nodiscard]] std::expected<void, Si4684Error> configureAfterBoot(
|
||||||
|
Si4684Band band);
|
||||||
|
|
||||||
|
Si4684Pins pins_;
|
||||||
|
const core::IFirmwareBlobReader& patch_;
|
||||||
|
const core::IFirmwareBlobReader& dabImage_;
|
||||||
|
const core::IFirmwareBlobReader& fmImage_;
|
||||||
|
bool booted_;
|
||||||
|
Si4684Band loadedBand_;
|
||||||
|
bool spiBusActive_;
|
||||||
|
void* spiDevice_;
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace si4684
|
||||||
@@ -0,0 +1,100 @@
|
|||||||
|
/**
|
||||||
|
* @file Si4684EmbeddedImages.hpp
|
||||||
|
* @brief Embedded Si4684 ROM patch, DAB and FM firmware blob accessors.
|
||||||
|
*
|
||||||
|
* DigiRadio firmware — https://github.com/manvalan/DigiRadio
|
||||||
|
*
|
||||||
|
* Copyright 2026 Michele Bigi
|
||||||
|
* SPDX-License-Identifier: Apache-2.0
|
||||||
|
*
|
||||||
|
* @author Michele Bigi
|
||||||
|
* @date 2026-07-06
|
||||||
|
*/
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "core/EmbeddedBlobReader.hpp"
|
||||||
|
#include "si4684/Si4684Band.hpp"
|
||||||
|
|
||||||
|
namespace si4684 {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Si4684EmbeddedImages — flash-backed Si4684 firmware blobs.
|
||||||
|
*
|
||||||
|
* @dname Si4684EmbeddedImages
|
||||||
|
* @return n/a (type)
|
||||||
|
* @pubstate Owns EmbeddedBlobReader views over EMBED_FILES symbols from
|
||||||
|
* Firmware/Si4684-Firmware/.
|
||||||
|
*
|
||||||
|
* @author Michele Bigi
|
||||||
|
* @date 2026-07-06
|
||||||
|
*/
|
||||||
|
class Si4684EmbeddedImages {
|
||||||
|
public:
|
||||||
|
/**
|
||||||
|
* @brief Si4684EmbeddedImages — bind linker-embedded binaries.
|
||||||
|
*
|
||||||
|
* @dname Si4684EmbeddedImages
|
||||||
|
* @pubstate constructs patch_, dab_, and fm_ readers from flash symbols.
|
||||||
|
*
|
||||||
|
* @author Michele Bigi
|
||||||
|
* @date 2026-07-06
|
||||||
|
*/
|
||||||
|
Si4684EmbeddedImages();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief romPatch — ROM patch blob for HOST_LOAD before main image.
|
||||||
|
*
|
||||||
|
* @dname romPatch
|
||||||
|
* @return Reader over rom_patch_016.bin.
|
||||||
|
* @pubstate reads patch_.
|
||||||
|
*
|
||||||
|
* @author Michele Bigi
|
||||||
|
* @date 2026-07-06
|
||||||
|
*/
|
||||||
|
[[nodiscard]] const core::IFirmwareBlobReader& romPatch() const noexcept;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief dabFirmware — DAB application image blob.
|
||||||
|
*
|
||||||
|
* @dname dabFirmware
|
||||||
|
* @return Reader over dab_firmware.bin.
|
||||||
|
* @pubstate reads dab_.
|
||||||
|
*
|
||||||
|
* @author Michele Bigi
|
||||||
|
* @date 2026-07-06
|
||||||
|
*/
|
||||||
|
[[nodiscard]] const core::IFirmwareBlobReader& dabFirmware() const noexcept;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief fmFirmware — FM application image blob.
|
||||||
|
*
|
||||||
|
* @dname fmFirmware
|
||||||
|
* @return Reader over fm_firmware.bin.
|
||||||
|
* @pubstate reads fm_.
|
||||||
|
*
|
||||||
|
* @author Michele Bigi
|
||||||
|
* @date 2026-07-06
|
||||||
|
*/
|
||||||
|
[[nodiscard]] const core::IFirmwareBlobReader& fmFirmware() const noexcept;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief applicationImage — map band to embedded application blob.
|
||||||
|
*
|
||||||
|
* @dname applicationImage
|
||||||
|
* @param band DAB or FM image selector.
|
||||||
|
* @return Reader for the selected application firmware.
|
||||||
|
* @pubstate reads dab_ or fm_.
|
||||||
|
*
|
||||||
|
* @author Michele Bigi
|
||||||
|
* @date 2026-07-06
|
||||||
|
*/
|
||||||
|
[[nodiscard]] const core::IFirmwareBlobReader& applicationImage(
|
||||||
|
Si4684Band band) const noexcept;
|
||||||
|
|
||||||
|
private:
|
||||||
|
core::EmbeddedBlobReader patch_;
|
||||||
|
core::EmbeddedBlobReader dab_;
|
||||||
|
core::EmbeddedBlobReader fm_;
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace si4684
|
||||||
@@ -0,0 +1,44 @@
|
|||||||
|
/**
|
||||||
|
* @file Si4684Error.hpp
|
||||||
|
* @brief Typed errors for Si4684 driver operations.
|
||||||
|
*
|
||||||
|
* DigiRadio firmware — https://github.com/manvalan/DigiRadio
|
||||||
|
*
|
||||||
|
* Copyright 2026 Michele Bigi
|
||||||
|
* SPDX-License-Identifier: Apache-2.0
|
||||||
|
*
|
||||||
|
* @author Michele Bigi
|
||||||
|
* @date 2026-07-06
|
||||||
|
*/
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
namespace si4684 {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Si4684Error — failure causes for Si4684 bring-up and tuning.
|
||||||
|
*
|
||||||
|
* @dname Si4684Error
|
||||||
|
* @return n/a (type)
|
||||||
|
* @pubstate n/a
|
||||||
|
*
|
||||||
|
* @author Michele Bigi
|
||||||
|
* @date 2026-07-06
|
||||||
|
*/
|
||||||
|
enum class Si4684Error {
|
||||||
|
SpiInitFailed,
|
||||||
|
ResetFailed,
|
||||||
|
CtsTimeout,
|
||||||
|
PowerUpFailed,
|
||||||
|
PatchLoadFailed,
|
||||||
|
ImageLoadFailed,
|
||||||
|
BootFailed,
|
||||||
|
NotBooted,
|
||||||
|
WrongBand,
|
||||||
|
CommandFailed,
|
||||||
|
StcTimeout,
|
||||||
|
TuneFailed,
|
||||||
|
ReplyTooShort,
|
||||||
|
BufferTooSmall,
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace si4684
|
||||||
@@ -0,0 +1,195 @@
|
|||||||
|
/**
|
||||||
|
* @file Si4684Tuner.hpp
|
||||||
|
* @brief core::ITuner adapter over Si4684Driver.
|
||||||
|
*
|
||||||
|
* DigiRadio firmware — https://github.com/manvalan/DigiRadio
|
||||||
|
*
|
||||||
|
* Copyright 2026 Michele Bigi
|
||||||
|
* SPDX-License-Identifier: Apache-2.0
|
||||||
|
*
|
||||||
|
* @author Michele Bigi
|
||||||
|
* @date 2026-07-06
|
||||||
|
*/
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "core/FrequencyKHz.hpp"
|
||||||
|
#include "core/ITuner.hpp"
|
||||||
|
#include "si4684/Si4684Driver.hpp"
|
||||||
|
|
||||||
|
namespace si4684 {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Si4684Tuner — maps Si4684Driver to core::ITuner.
|
||||||
|
*
|
||||||
|
* @dname Si4684Tuner
|
||||||
|
* @param driver Borrowed Si4684Driver (must outlive this adapter).
|
||||||
|
* @return n/a (type)
|
||||||
|
* @pubstate Borrows driver_. Caches last DAB index, FM frequency, and volume
|
||||||
|
* for status reporting. No public data members.
|
||||||
|
*
|
||||||
|
* Translates domain calls into SPI commands and maps Si4684Error to
|
||||||
|
* core::TunerError at this boundary.
|
||||||
|
*
|
||||||
|
* @author Michele Bigi
|
||||||
|
* @date 2026-07-06
|
||||||
|
*/
|
||||||
|
class Si4684Tuner : public core::ITuner {
|
||||||
|
public:
|
||||||
|
/**
|
||||||
|
* @brief Si4684Tuner — bind to a booted or bootable driver instance.
|
||||||
|
*
|
||||||
|
* @dname Si4684Tuner
|
||||||
|
* @param driver Si4684 driver constructed by HardwareBootstrap.
|
||||||
|
* @pubstate stores driver reference; sets default tune targets.
|
||||||
|
*
|
||||||
|
* @author Michele Bigi
|
||||||
|
* @date 2026-07-06
|
||||||
|
*/
|
||||||
|
explicit Si4684Tuner(Si4684Driver& driver);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief boot — load the requested Si4684 application image.
|
||||||
|
*
|
||||||
|
* @dname boot
|
||||||
|
* @param band DAB or FM image to load.
|
||||||
|
* @return Ok on success, or a mapped TunerError.
|
||||||
|
* @pubstate delegates to driver_.boot().
|
||||||
|
*
|
||||||
|
* @author Michele Bigi
|
||||||
|
* @date 2026-07-06
|
||||||
|
*/
|
||||||
|
[[nodiscard]] std::expected<void, core::TunerError> boot(
|
||||||
|
core::TunerBand band) override;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief currentBand — read the loaded application band.
|
||||||
|
*
|
||||||
|
* @dname currentBand
|
||||||
|
* @return Active TunerBand, or TunerError::NotBooted.
|
||||||
|
* @pubstate reads driver_.loadedBand().
|
||||||
|
*
|
||||||
|
* @author Michele Bigi
|
||||||
|
* @date 2026-07-06
|
||||||
|
*/
|
||||||
|
[[nodiscard]] std::expected<core::TunerBand, core::TunerError> currentBand()
|
||||||
|
const override;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief readStatus — build a core TunerStatus from driver metrics.
|
||||||
|
*
|
||||||
|
* @dname readStatus
|
||||||
|
* @return TunerStatus on success, or a mapped TunerError.
|
||||||
|
* @pubstate reads driver_; refreshes cached tune targets from RSQ/DIGRAD.
|
||||||
|
*
|
||||||
|
* @author Michele Bigi
|
||||||
|
* @date 2026-07-06
|
||||||
|
*/
|
||||||
|
[[nodiscard]] std::expected<core::TunerStatus, core::TunerError> readStatus()
|
||||||
|
override;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief tuneDab — tune to a Band III ensemble index.
|
||||||
|
*
|
||||||
|
* @dname tuneDab
|
||||||
|
* @param freqIndex Ensemble index 0–37.
|
||||||
|
* @return Ok on success, or a mapped TunerError.
|
||||||
|
* @pubstate writes dabIndex_ on success.
|
||||||
|
*
|
||||||
|
* @author Michele Bigi
|
||||||
|
* @date 2026-07-06
|
||||||
|
*/
|
||||||
|
[[nodiscard]] std::expected<void, core::TunerError> tuneDab(
|
||||||
|
std::uint8_t freqIndex) override;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief tuneFm — tune to an FM centre frequency in kHz.
|
||||||
|
*
|
||||||
|
* @dname tuneFm
|
||||||
|
* @param frequency Validated FM centre frequency.
|
||||||
|
* @return Ok on success, or a mapped TunerError.
|
||||||
|
* @pubstate writes fmFrequency_ on success.
|
||||||
|
*
|
||||||
|
* @author Michele Bigi
|
||||||
|
* @date 2026-07-06
|
||||||
|
*/
|
||||||
|
[[nodiscard]] std::expected<void, core::TunerError> tuneFm(
|
||||||
|
core::FrequencyKHz frequency) override;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief seekFm — seek FM with band wrap.
|
||||||
|
*
|
||||||
|
* @dname seekFm
|
||||||
|
* @param direction Up or Down scan direction.
|
||||||
|
* @return New centre frequency, or a mapped TunerError.
|
||||||
|
* @pubstate writes fmFrequency_ on success.
|
||||||
|
*
|
||||||
|
* @author Michele Bigi
|
||||||
|
* @date 2026-07-06
|
||||||
|
*/
|
||||||
|
[[nodiscard]] std::expected<core::FrequencyKHz, core::TunerError> seekFm(
|
||||||
|
core::SeekDirection direction) override;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief listDabServices — fetch programmes for the current ensemble.
|
||||||
|
*
|
||||||
|
* @dname listDabServices
|
||||||
|
* @return Service entries, ServiceListEmpty, or a mapped TunerError.
|
||||||
|
* @pubstate reads driver_ service list when ready.
|
||||||
|
*
|
||||||
|
* @author Michele Bigi
|
||||||
|
* @date 2026-07-06
|
||||||
|
*/
|
||||||
|
[[nodiscard]] std::expected<std::vector<core::TunerServiceEntry>,
|
||||||
|
core::TunerError>
|
||||||
|
listDabServices() override;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief playDabService — start DAB audio output.
|
||||||
|
*
|
||||||
|
* @dname playDabService
|
||||||
|
* @param serviceId Selected service identifier.
|
||||||
|
* @param componentId Audio component within the service.
|
||||||
|
* @return Ok on success, or a mapped TunerError.
|
||||||
|
* @pubstate delegates to driver_.startDabService().
|
||||||
|
*
|
||||||
|
* @author Michele Bigi
|
||||||
|
* @date 2026-07-06
|
||||||
|
*/
|
||||||
|
[[nodiscard]] std::expected<void, core::TunerError> playDabService(
|
||||||
|
std::uint32_t serviceId, std::uint32_t componentId) override;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief setVolume — set Si4684 output attenuation.
|
||||||
|
*
|
||||||
|
* @dname setVolume
|
||||||
|
* @param level Attenuator 0–63.
|
||||||
|
* @return Ok on success, or a mapped TunerError.
|
||||||
|
* @pubstate writes volume_ on success.
|
||||||
|
*
|
||||||
|
* @author Michele Bigi
|
||||||
|
* @date 2026-07-06
|
||||||
|
*/
|
||||||
|
[[nodiscard]] std::expected<void, core::TunerError> setVolume(
|
||||||
|
std::uint8_t level) override;
|
||||||
|
|
||||||
|
private:
|
||||||
|
/**
|
||||||
|
* @brief mapError — translate Si4684Error to core::TunerError.
|
||||||
|
*
|
||||||
|
* @dname mapError
|
||||||
|
* @param error Driver-level failure cause.
|
||||||
|
* @return Equivalent TunerError for services and HTTP layer.
|
||||||
|
* @pubstate none
|
||||||
|
*
|
||||||
|
* @author Michele Bigi
|
||||||
|
* @date 2026-07-06
|
||||||
|
*/
|
||||||
|
[[nodiscard]] static core::TunerError mapError(Si4684Error error) noexcept;
|
||||||
|
|
||||||
|
Si4684Driver& driver_;
|
||||||
|
std::uint8_t dabIndex_;
|
||||||
|
core::FrequencyKHz fmFrequency_;
|
||||||
|
std::uint8_t volume_;
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace si4684
|
||||||
@@ -0,0 +1,176 @@
|
|||||||
|
/**
|
||||||
|
* @file Si4684Types.hpp
|
||||||
|
* @brief Domain types for Si4684 tuner operations (DAB/FM status, services).
|
||||||
|
*
|
||||||
|
* DigiRadio firmware — https://github.com/manvalan/DigiRadio
|
||||||
|
*
|
||||||
|
* Copyright 2026 Michele Bigi
|
||||||
|
* SPDX-License-Identifier: Apache-2.0
|
||||||
|
*
|
||||||
|
* @author Michele Bigi
|
||||||
|
* @date 2026-07-06
|
||||||
|
*/
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "core/FrequencyKHz.hpp"
|
||||||
|
#include "core/SeekDirection.hpp"
|
||||||
|
|
||||||
|
#include <array>
|
||||||
|
#include <cstddef>
|
||||||
|
#include <cstdint>
|
||||||
|
|
||||||
|
namespace si4684 {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Si4684DigitalServiceType — START_DIGITAL_SERVICE mode byte.
|
||||||
|
*
|
||||||
|
* @dname Si4684DigitalServiceType
|
||||||
|
* @return n/a (type)
|
||||||
|
* @pubstate n/a
|
||||||
|
*
|
||||||
|
* @author Michele Bigi
|
||||||
|
* @date 2026-07-06
|
||||||
|
*/
|
||||||
|
enum class Si4684DigitalServiceType : std::uint8_t {
|
||||||
|
Audio = 0x00,
|
||||||
|
Packet = 0x01,
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief SeekBandWrap — FM seek band-wrap behaviour (FM_SEEK_START).
|
||||||
|
*
|
||||||
|
* @dname SeekBandWrap
|
||||||
|
* @return n/a (type)
|
||||||
|
* @pubstate n/a
|
||||||
|
*
|
||||||
|
* @author Michele Bigi
|
||||||
|
* @date 2026-07-06
|
||||||
|
*/
|
||||||
|
enum class SeekBandWrap { Wrap, NoWrap };
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Si4684PartInfo — chip identity from GET_PART_INFO (AN649).
|
||||||
|
*
|
||||||
|
* @dname Si4684PartInfo
|
||||||
|
* @return n/a (type)
|
||||||
|
* @pubstate Plain DTO from GET_PART_INFO response bytes.
|
||||||
|
*
|
||||||
|
* @author Michele Bigi
|
||||||
|
* @date 2026-07-06
|
||||||
|
*/
|
||||||
|
struct Si4684PartInfo {
|
||||||
|
std::uint16_t chipId; ///< Part identifier from the chip.
|
||||||
|
std::uint8_t firmwareMajor; ///< Loaded firmware major version.
|
||||||
|
std::uint8_t firmwareMinor; ///< Loaded firmware minor version.
|
||||||
|
std::uint8_t firmwareBuild; ///< Loaded firmware build number.
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Si4684SysState — running application after boot.
|
||||||
|
*
|
||||||
|
* @dname Si4684SysState
|
||||||
|
* @return n/a (type)
|
||||||
|
* @pubstate Plain DTO from GET_SYS_STATE.
|
||||||
|
*
|
||||||
|
* @author Michele Bigi
|
||||||
|
* @date 2026-07-06
|
||||||
|
*/
|
||||||
|
struct Si4684SysState {
|
||||||
|
std::uint8_t imageType; ///< Loaded image type byte from the chip.
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Si4684FmRsq — FM received-signal quality (FM_RSQ_STATUS).
|
||||||
|
*
|
||||||
|
* @dname Si4684FmRsq
|
||||||
|
* @return n/a (type)
|
||||||
|
* @pubstate Plain DTO from FM_RSQ_STATUS response bytes.
|
||||||
|
*
|
||||||
|
* @author Michele Bigi
|
||||||
|
* @date 2026-07-06
|
||||||
|
*/
|
||||||
|
struct Si4684FmRsq {
|
||||||
|
core::FrequencyKHz frequency; ///< Tuned centre frequency in kHz.
|
||||||
|
std::int8_t rssiDbuV; ///< RSSI in dBµV.
|
||||||
|
std::int8_t snrDb; ///< SNR in dB.
|
||||||
|
bool valid; ///< RSQ valid flag from the chip.
|
||||||
|
bool stereo; ///< Stereo pilot detected.
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Si4684FmRdsStatus — raw RDS group snapshot (FM_RDS_STATUS).
|
||||||
|
*
|
||||||
|
* @dname Si4684FmRdsStatus
|
||||||
|
* @return n/a (type)
|
||||||
|
* @pubstate Plain DTO from FM_RDS_STATUS response bytes.
|
||||||
|
*
|
||||||
|
* @author Michele Bigi
|
||||||
|
* @date 2026-07-06
|
||||||
|
*/
|
||||||
|
struct Si4684FmRdsStatus {
|
||||||
|
std::uint16_t blockA; ///< RDS block A.
|
||||||
|
std::uint16_t blockB; ///< RDS block B.
|
||||||
|
std::uint16_t blockC; ///< RDS block C.
|
||||||
|
std::uint16_t blockD; ///< RDS block D.
|
||||||
|
bool received; ///< Group received flag.
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Si4684DabDigRadStatus — ensemble lock metrics (DAB_DIGRAD_STATUS).
|
||||||
|
*
|
||||||
|
* @dname Si4684DabDigRadStatus
|
||||||
|
* @return n/a (type)
|
||||||
|
* @pubstate Plain DTO from DAB_DIGRAD_STATUS response bytes.
|
||||||
|
*
|
||||||
|
* @author Michele Bigi
|
||||||
|
* @date 2026-07-06
|
||||||
|
*/
|
||||||
|
struct Si4684DabDigRadStatus {
|
||||||
|
std::uint8_t ficQuality; ///< FIC quality 0–100.
|
||||||
|
std::uint8_t cnrDb; ///< CNR in dB.
|
||||||
|
bool acquired; ///< Ensemble acquired flag.
|
||||||
|
bool valid; ///< DIGRAD valid flag.
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Si4684DabEventStatus — DAB event flags (DAB_GET_EVENT_STATUS).
|
||||||
|
*
|
||||||
|
* @dname Si4684DabEventStatus
|
||||||
|
* @return n/a (type)
|
||||||
|
* @pubstate Plain DTO from DAB_GET_EVENT_STATUS response bytes.
|
||||||
|
*
|
||||||
|
* @author Michele Bigi
|
||||||
|
* @date 2026-07-06
|
||||||
|
*/
|
||||||
|
struct Si4684DabEventStatus {
|
||||||
|
bool serviceListReady; ///< Service list available for fetch.
|
||||||
|
bool reconfig; ///< Ensemble reconfiguration event.
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Si4684DabService — one row from GET_DIGITAL_SERVICE_LIST.
|
||||||
|
*
|
||||||
|
* @dname Si4684DabService
|
||||||
|
* @return n/a (type)
|
||||||
|
* @pubstate Plain DTO parsed from a service-list chunk.
|
||||||
|
*
|
||||||
|
* @author Michele Bigi
|
||||||
|
* @date 2026-07-06
|
||||||
|
*/
|
||||||
|
struct Si4684DabService {
|
||||||
|
std::uint32_t serviceId; ///< DAB service identifier.
|
||||||
|
std::uint32_t componentId; ///< Audio component identifier.
|
||||||
|
std::array<char, 17> label; ///< Programme label, NUL-terminated.
|
||||||
|
std::uint8_t serviceType; ///< Service type byte from the list.
|
||||||
|
};
|
||||||
|
|
||||||
|
/** Band III DAB channel plan (kHz), PE5PVB / ETSI EN 300 401 Table 14. */
|
||||||
|
inline constexpr std::array<std::uint32_t, 38> kDefaultDabFrequencyKhz = {
|
||||||
|
174928, 176640, 178352, 180064, 181936, 183648, 185360, 187072, 188928,
|
||||||
|
190640, 192352, 194064, 195936, 197648, 199360, 201072, 202928, 204640,
|
||||||
|
206352, 208064, 209936, 211648, 213360, 215072, 216928, 218640, 220352,
|
||||||
|
222064, 223936, 225648, 227360, 229072, 230784, 232496, 234208, 235776,
|
||||||
|
237488, 239200,
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace si4684
|
||||||
@@ -0,0 +1,776 @@
|
|||||||
|
/**
|
||||||
|
* @file Si4684Driver.cpp
|
||||||
|
* @brief Si4684Driver implementation.
|
||||||
|
*
|
||||||
|
* DigiRadio firmware — https://github.com/manvalan/DigiRadio
|
||||||
|
*
|
||||||
|
* Copyright 2026 Michele Bigi
|
||||||
|
* SPDX-License-Identifier: Apache-2.0
|
||||||
|
*
|
||||||
|
* @author Michele Bigi
|
||||||
|
* @date 2026-07-06
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include "si4684/Si4684Driver.hpp"
|
||||||
|
|
||||||
|
#include "driver/gpio.h"
|
||||||
|
#include "driver/spi_master.h"
|
||||||
|
#include "esp_log.h"
|
||||||
|
#include "freertos/FreeRTOS.h"
|
||||||
|
#include "freertos/task.h"
|
||||||
|
|
||||||
|
#include <algorithm>
|
||||||
|
#include <array>
|
||||||
|
#include <cstring>
|
||||||
|
#include <span>
|
||||||
|
|
||||||
|
namespace si4684 {
|
||||||
|
|
||||||
|
namespace {
|
||||||
|
|
||||||
|
constexpr char kTag[] = "Si4684";
|
||||||
|
constexpr std::size_t kSpiBufferSize = 4096U;
|
||||||
|
constexpr int kCtsPollMs = 2;
|
||||||
|
constexpr int kCtsRetries = 200;
|
||||||
|
constexpr int kStcRetries = 250;
|
||||||
|
constexpr int kStcPollMs = 20;
|
||||||
|
|
||||||
|
constexpr std::uint16_t kPropDigitalIoOutputSelect = 0x0200U;
|
||||||
|
constexpr std::uint16_t kPropDigitalIoSampleRate = 0x0201U;
|
||||||
|
constexpr std::uint16_t kPropPinConfigEnable = 0x0800U;
|
||||||
|
constexpr std::uint16_t kPropAudioVolume = 0x0300U;
|
||||||
|
constexpr std::uint16_t kPropFmRdsConfig = 0x3C02U;
|
||||||
|
constexpr std::uint16_t kPropDabTuneFeCfg = 0x1712U;
|
||||||
|
constexpr std::uint16_t kPropDabXpadEnable = 0xB400U;
|
||||||
|
constexpr std::uint16_t kPropDigitalServiceIntSource = 0x8100U;
|
||||||
|
|
||||||
|
std::uint16_t readLe16(const std::uint8_t* p)
|
||||||
|
{
|
||||||
|
return static_cast<std::uint16_t>(p[0] | (static_cast<std::uint16_t>(p[1]) << 8));
|
||||||
|
}
|
||||||
|
|
||||||
|
std::uint32_t readLe32(const std::uint8_t* p)
|
||||||
|
{
|
||||||
|
return static_cast<std::uint32_t>(p[0])
|
||||||
|
| (static_cast<std::uint32_t>(p[1]) << 8)
|
||||||
|
| (static_cast<std::uint32_t>(p[2]) << 16)
|
||||||
|
| (static_cast<std::uint32_t>(p[3]) << 24);
|
||||||
|
}
|
||||||
|
|
||||||
|
/** FM_TUNE_FREQ uses 10 kHz units (AN649); API uses kHz. */
|
||||||
|
[[nodiscard]] std::uint16_t kHzToChipFmFreq(std::uint32_t frequencyKhz)
|
||||||
|
{
|
||||||
|
return static_cast<std::uint16_t>(frequencyKhz / 10U);
|
||||||
|
}
|
||||||
|
|
||||||
|
[[nodiscard]] std::uint32_t chipFmFreqToKHz(std::uint16_t chipFreq)
|
||||||
|
{
|
||||||
|
return static_cast<std::uint32_t>(chipFreq) * 10U;
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace
|
||||||
|
|
||||||
|
Si4684Driver::Si4684Driver(Si4684Pins pins,
|
||||||
|
const core::IFirmwareBlobReader& patch,
|
||||||
|
const core::IFirmwareBlobReader& dabImage,
|
||||||
|
const core::IFirmwareBlobReader& fmImage)
|
||||||
|
: pins_(pins)
|
||||||
|
, patch_(patch)
|
||||||
|
, dabImage_(dabImage)
|
||||||
|
, fmImage_(fmImage)
|
||||||
|
, booted_(false)
|
||||||
|
, loadedBand_(Si4684Band::Dab)
|
||||||
|
, spiBusActive_(false)
|
||||||
|
, spiDevice_(nullptr)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
Si4684Driver::~Si4684Driver()
|
||||||
|
{
|
||||||
|
if (spiDevice_ != nullptr) {
|
||||||
|
spi_bus_remove_device(static_cast<spi_device_handle_t>(spiDevice_));
|
||||||
|
spiDevice_ = nullptr;
|
||||||
|
}
|
||||||
|
if (spiBusActive_) {
|
||||||
|
spi_bus_free(static_cast<spi_host_device_t>(pins_.spiHost));
|
||||||
|
spiBusActive_ = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
std::expected<void, Si4684Error> Si4684Driver::ensureBooted() const
|
||||||
|
{
|
||||||
|
if (!booted_) {
|
||||||
|
return std::unexpected(Si4684Error::NotBooted);
|
||||||
|
}
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
|
||||||
|
std::expected<void, Si4684Error> Si4684Driver::ensureBand(
|
||||||
|
Si4684Band band) const
|
||||||
|
{
|
||||||
|
if (auto ready = ensureBooted(); !ready) {
|
||||||
|
return ready;
|
||||||
|
}
|
||||||
|
if (loadedBand_ != band) {
|
||||||
|
return std::unexpected(Si4684Error::WrongBand);
|
||||||
|
}
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
|
||||||
|
std::expected<void, Si4684Error> Si4684Driver::waitCts()
|
||||||
|
{
|
||||||
|
std::array<std::uint8_t, 5> pollTx = {};
|
||||||
|
std::array<std::uint8_t, 5> pollRx = {};
|
||||||
|
|
||||||
|
for (int attempt = 0; attempt < kCtsRetries; ++attempt) {
|
||||||
|
vTaskDelay(pdMS_TO_TICKS(kCtsPollMs));
|
||||||
|
spi_transaction_t txn = {};
|
||||||
|
txn.length = pollTx.size() * 8U;
|
||||||
|
txn.tx_buffer = pollTx.data();
|
||||||
|
txn.rx_buffer = pollRx.data();
|
||||||
|
if (spi_device_transmit(static_cast<spi_device_handle_t>(spiDevice_),
|
||||||
|
&txn) != ESP_OK) {
|
||||||
|
return std::unexpected(Si4684Error::SpiInitFailed);
|
||||||
|
}
|
||||||
|
if ((pollRx[1] & 0x80U) != 0U) {
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return std::unexpected(Si4684Error::CtsTimeout);
|
||||||
|
}
|
||||||
|
|
||||||
|
std::expected<void, Si4684Error> Si4684Driver::waitStc()
|
||||||
|
{
|
||||||
|
std::array<std::uint8_t, 5> pollTx = {};
|
||||||
|
std::array<std::uint8_t, 5> pollRx = {};
|
||||||
|
|
||||||
|
for (int attempt = 0; attempt < kStcRetries; ++attempt) {
|
||||||
|
vTaskDelay(pdMS_TO_TICKS(kStcPollMs));
|
||||||
|
spi_transaction_t txn = {};
|
||||||
|
txn.length = pollTx.size() * 8U;
|
||||||
|
txn.tx_buffer = pollTx.data();
|
||||||
|
txn.rx_buffer = pollRx.data();
|
||||||
|
if (spi_device_transmit(static_cast<spi_device_handle_t>(spiDevice_),
|
||||||
|
&txn) != ESP_OK) {
|
||||||
|
return std::unexpected(Si4684Error::SpiInitFailed);
|
||||||
|
}
|
||||||
|
if ((pollRx[1] & 0x01U) != 0U) {
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return std::unexpected(Si4684Error::StcTimeout);
|
||||||
|
}
|
||||||
|
|
||||||
|
std::expected<void, Si4684Error> Si4684Driver::sendCommand(
|
||||||
|
std::span<const std::uint8_t> bytes)
|
||||||
|
{
|
||||||
|
if (bytes.empty() || bytes.size() > kSpiBufferSize) {
|
||||||
|
return std::unexpected(Si4684Error::CommandFailed);
|
||||||
|
}
|
||||||
|
spi_transaction_t txn = {};
|
||||||
|
txn.length = bytes.size() * 8U;
|
||||||
|
txn.tx_buffer = bytes.data();
|
||||||
|
if (spi_device_transmit(static_cast<spi_device_handle_t>(spiDevice_),
|
||||||
|
&txn) != ESP_OK) {
|
||||||
|
return std::unexpected(Si4684Error::SpiInitFailed);
|
||||||
|
}
|
||||||
|
return waitCts();
|
||||||
|
}
|
||||||
|
|
||||||
|
std::expected<void, Si4684Error> Si4684Driver::readRaw(
|
||||||
|
std::span<std::uint8_t> buffer)
|
||||||
|
{
|
||||||
|
if (buffer.empty() || buffer.size() > kSpiBufferSize) {
|
||||||
|
return std::unexpected(Si4684Error::ReplyTooShort);
|
||||||
|
}
|
||||||
|
std::fill(buffer.begin(), buffer.end(), 0U);
|
||||||
|
spi_transaction_t txn = {};
|
||||||
|
txn.length = buffer.size() * 8U;
|
||||||
|
txn.tx_buffer = buffer.data();
|
||||||
|
txn.rx_buffer = buffer.data();
|
||||||
|
if (spi_device_transmit(static_cast<spi_device_handle_t>(spiDevice_),
|
||||||
|
&txn) != ESP_OK) {
|
||||||
|
return std::unexpected(Si4684Error::SpiInitFailed);
|
||||||
|
}
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
|
||||||
|
std::expected<void, Si4684Error> Si4684Driver::writeCommand(
|
||||||
|
Command cmd, const std::uint8_t* payload, std::size_t length)
|
||||||
|
{
|
||||||
|
if (length + 2U > kSpiBufferSize) {
|
||||||
|
return std::unexpected(Si4684Error::CommandFailed);
|
||||||
|
}
|
||||||
|
std::array<std::uint8_t, kSpiBufferSize> buffer = {};
|
||||||
|
buffer[0] = static_cast<std::uint8_t>(cmd);
|
||||||
|
buffer[1] = 0x00U;
|
||||||
|
if (payload != nullptr && length > 0U) {
|
||||||
|
std::memcpy(buffer.data() + 2U, payload, length);
|
||||||
|
}
|
||||||
|
return sendCommand({buffer.data(), 2U + length});
|
||||||
|
}
|
||||||
|
|
||||||
|
std::expected<void, Si4684Error> Si4684Driver::hostLoadBlob(
|
||||||
|
const core::IFirmwareBlobReader& blob, std::size_t chunkPayload)
|
||||||
|
{
|
||||||
|
std::array<std::byte, 2044> payload = {};
|
||||||
|
std::size_t offset = 0U;
|
||||||
|
|
||||||
|
while (offset < blob.size()) {
|
||||||
|
const std::size_t maxChunk = std::min(chunkPayload, payload.size());
|
||||||
|
const std::size_t copied =
|
||||||
|
blob.read(offset, std::span<std::byte>(payload.data(), maxChunk));
|
||||||
|
if (copied == 0U) {
|
||||||
|
return std::unexpected(Si4684Error::ImageLoadFailed);
|
||||||
|
}
|
||||||
|
|
||||||
|
std::array<std::uint8_t, kSpiBufferSize> tx = {};
|
||||||
|
tx[0] = static_cast<std::uint8_t>(Command::HostLoad);
|
||||||
|
tx[1] = 0x00U;
|
||||||
|
tx[2] = 0x00U;
|
||||||
|
tx[3] = 0x00U;
|
||||||
|
std::memcpy(tx.data() + 4U, payload.data(), copied);
|
||||||
|
|
||||||
|
spi_transaction_t txn = {};
|
||||||
|
txn.length = (4U + copied) * 8U;
|
||||||
|
txn.tx_buffer = tx.data();
|
||||||
|
if (spi_device_transmit(static_cast<spi_device_handle_t>(spiDevice_),
|
||||||
|
&txn) != ESP_OK) {
|
||||||
|
return std::unexpected(Si4684Error::ImageLoadFailed);
|
||||||
|
}
|
||||||
|
if (auto cts = waitCts(); !cts) {
|
||||||
|
return cts;
|
||||||
|
}
|
||||||
|
offset += copied;
|
||||||
|
}
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
|
||||||
|
std::expected<void, Si4684Error> Si4684Driver::setProperty(
|
||||||
|
std::uint16_t propertyId, std::uint16_t value)
|
||||||
|
{
|
||||||
|
if (auto ready = ensureBooted(); !ready) {
|
||||||
|
return ready;
|
||||||
|
}
|
||||||
|
const std::uint8_t args[] = {
|
||||||
|
static_cast<std::uint8_t>(propertyId & 0xFFU),
|
||||||
|
static_cast<std::uint8_t>(propertyId >> 8),
|
||||||
|
static_cast<std::uint8_t>(value & 0xFFU),
|
||||||
|
static_cast<std::uint8_t>(value >> 8),
|
||||||
|
};
|
||||||
|
if (auto cmd = writeCommand(Command::SetProperty, args, sizeof(args));
|
||||||
|
!cmd) {
|
||||||
|
return std::unexpected(Si4684Error::CommandFailed);
|
||||||
|
}
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
|
||||||
|
std::expected<void, Si4684Error> Si4684Driver::setVolume(std::uint8_t level)
|
||||||
|
{
|
||||||
|
return setProperty(kPropAudioVolume,
|
||||||
|
static_cast<std::uint16_t>(level & 0x3FU));
|
||||||
|
}
|
||||||
|
|
||||||
|
std::expected<Si4684PartInfo, Si4684Error> Si4684Driver::getPartInfo()
|
||||||
|
{
|
||||||
|
if (auto ready = ensureBooted(); !ready) {
|
||||||
|
return std::unexpected(ready.error());
|
||||||
|
}
|
||||||
|
if (auto cmd = writeCommand(Command::GetPartInfo, nullptr, 0U); !cmd) {
|
||||||
|
return std::unexpected(Si4684Error::CommandFailed);
|
||||||
|
}
|
||||||
|
std::array<std::uint8_t, 24> raw = {};
|
||||||
|
if (auto rd = readRaw(raw); !rd) {
|
||||||
|
return std::unexpected(rd.error());
|
||||||
|
}
|
||||||
|
if (auto fn = writeCommand(Command::GetFuncInfo, nullptr, 0U); !fn) {
|
||||||
|
return std::unexpected(Si4684Error::CommandFailed);
|
||||||
|
}
|
||||||
|
std::array<std::uint8_t, 13> fnRaw = {};
|
||||||
|
if (auto rd = readRaw(fnRaw); !rd) {
|
||||||
|
return std::unexpected(rd.error());
|
||||||
|
}
|
||||||
|
|
||||||
|
Si4684PartInfo info = {};
|
||||||
|
info.chipId = readLe16(raw.data() + 9);
|
||||||
|
info.firmwareMajor = fnRaw[5];
|
||||||
|
info.firmwareMinor = fnRaw[6];
|
||||||
|
info.firmwareBuild = fnRaw[7];
|
||||||
|
return info;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::expected<Si4684SysState, Si4684Error> Si4684Driver::getSysState()
|
||||||
|
{
|
||||||
|
if (auto ready = ensureBooted(); !ready) {
|
||||||
|
return std::unexpected(ready.error());
|
||||||
|
}
|
||||||
|
if (auto cmd = writeCommand(Command::GetSysState, nullptr, 0U); !cmd) {
|
||||||
|
return std::unexpected(Si4684Error::CommandFailed);
|
||||||
|
}
|
||||||
|
std::array<std::uint8_t, 7> raw = {};
|
||||||
|
if (auto rd = readRaw(raw); !rd) {
|
||||||
|
return std::unexpected(rd.error());
|
||||||
|
}
|
||||||
|
Si4684SysState state = {};
|
||||||
|
state.imageType = raw[5];
|
||||||
|
return state;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::expected<void, Si4684Error> Si4684Driver::configureAfterBoot(
|
||||||
|
Si4684Band band)
|
||||||
|
{
|
||||||
|
if (band == Si4684Band::Dab) {
|
||||||
|
if (auto plan = installDefaultDabFrequencyPlan(); !plan) {
|
||||||
|
return plan;
|
||||||
|
}
|
||||||
|
static constexpr std::uint16_t kDabProps[][2] = {
|
||||||
|
{0x0202U, 0x1600U},
|
||||||
|
{0x1710U, 0xFC4AU},
|
||||||
|
{0x1711U, 0x00F8U},
|
||||||
|
{0x8101U, 0x0064U},
|
||||||
|
{0xB200U, 0x0000U},
|
||||||
|
{0xB201U, 0x0080U},
|
||||||
|
{0xB301U, 0x0000U},
|
||||||
|
{0xB302U, 0x0000U},
|
||||||
|
{0xB303U, 0x0000U},
|
||||||
|
{0xB401U, 0x0002U},
|
||||||
|
{0xB500U, 0x0000U},
|
||||||
|
};
|
||||||
|
for (const auto& prop : kDabProps) {
|
||||||
|
if (auto set = setProperty(prop[0], prop[1]); !set) {
|
||||||
|
return set;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (auto xpad = setProperty(kPropDabXpadEnable, 0x0097U); !xpad) {
|
||||||
|
return xpad;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if (auto rds = setProperty(kPropFmRdsConfig, 0x0001U); !rds) {
|
||||||
|
return rds;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (auto i2s = setProperty(kPropDigitalIoOutputSelect, 0x8000U); !i2s) {
|
||||||
|
return i2s;
|
||||||
|
}
|
||||||
|
if (auto rate = setProperty(kPropDigitalIoSampleRate, 0xAC44U); !rate) {
|
||||||
|
return rate;
|
||||||
|
}
|
||||||
|
if (auto pins = setProperty(kPropPinConfigEnable, 0x0003U); !pins) {
|
||||||
|
return pins;
|
||||||
|
}
|
||||||
|
if (auto dabFe = setProperty(kPropDabTuneFeCfg, 0x0001U); !dabFe) {
|
||||||
|
return dabFe;
|
||||||
|
}
|
||||||
|
if (auto dsrv = setProperty(kPropDigitalServiceIntSource, 0x0001U);
|
||||||
|
!dsrv) {
|
||||||
|
return dsrv;
|
||||||
|
}
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
|
||||||
|
std::expected<void, Si4684Error> Si4684Driver::boot(Si4684Band band)
|
||||||
|
{
|
||||||
|
if (booted_ && loadedBand_ == band) {
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
|
||||||
|
if (booted_) {
|
||||||
|
booted_ = false;
|
||||||
|
if (spiDevice_ != nullptr) {
|
||||||
|
spi_bus_remove_device(static_cast<spi_device_handle_t>(spiDevice_));
|
||||||
|
spiDevice_ = nullptr;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const core::IFirmwareBlobReader& image =
|
||||||
|
(band == Si4684Band::Fm) ? fmImage_ : dabImage_;
|
||||||
|
|
||||||
|
gpio_config_t rstCfg = {};
|
||||||
|
rstCfg.pin_bit_mask = 1ULL << pins_.rstbGpio;
|
||||||
|
rstCfg.mode = GPIO_MODE_OUTPUT;
|
||||||
|
if (gpio_config(&rstCfg) != ESP_OK) {
|
||||||
|
return std::unexpected(Si4684Error::ResetFailed);
|
||||||
|
}
|
||||||
|
gpio_set_level(static_cast<gpio_num_t>(pins_.rstbGpio), 0);
|
||||||
|
vTaskDelay(pdMS_TO_TICKS(5));
|
||||||
|
gpio_set_level(static_cast<gpio_num_t>(pins_.rstbGpio), 1);
|
||||||
|
vTaskDelay(pdMS_TO_TICKS(3));
|
||||||
|
|
||||||
|
if (!spiBusActive_) {
|
||||||
|
spi_bus_config_t busCfg = {};
|
||||||
|
busCfg.miso_io_num = pins_.misoGpio;
|
||||||
|
busCfg.mosi_io_num = pins_.mosiGpio;
|
||||||
|
busCfg.sclk_io_num = pins_.sclkGpio;
|
||||||
|
busCfg.quadwp_io_num = -1;
|
||||||
|
busCfg.quadhd_io_num = -1;
|
||||||
|
busCfg.max_transfer_sz = static_cast<int>(kSpiBufferSize);
|
||||||
|
|
||||||
|
if (spi_bus_initialize(static_cast<spi_host_device_t>(pins_.spiHost),
|
||||||
|
&busCfg, SPI_DMA_CH_AUTO) != ESP_OK) {
|
||||||
|
return std::unexpected(Si4684Error::SpiInitFailed);
|
||||||
|
}
|
||||||
|
spiBusActive_ = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (spiDevice_ == nullptr) {
|
||||||
|
spi_device_interface_config_t devCfg = {};
|
||||||
|
devCfg.clock_speed_hz = 10 * 1000 * 1000;
|
||||||
|
devCfg.mode = 0;
|
||||||
|
devCfg.spics_io_num = pins_.csGpio;
|
||||||
|
devCfg.queue_size = 1;
|
||||||
|
|
||||||
|
spi_device_handle_t dev = nullptr;
|
||||||
|
if (spi_bus_add_device(static_cast<spi_host_device_t>(pins_.spiHost),
|
||||||
|
&devCfg, &dev) != ESP_OK) {
|
||||||
|
return std::unexpected(Si4684Error::SpiInitFailed);
|
||||||
|
}
|
||||||
|
spiDevice_ = dev;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (auto st = writeCommand(Command::GetSysState, nullptr, 0U); !st) {
|
||||||
|
return st;
|
||||||
|
}
|
||||||
|
|
||||||
|
const std::uint8_t powerUp[] = {
|
||||||
|
0x17, 0x48, 0x00, 0xf8, 0x24, 0x01, 0x1F, 0x10,
|
||||||
|
0x00, 0x00, 0x00, 0x18, 0x00, 0x00,
|
||||||
|
};
|
||||||
|
if (auto pu = writeCommand(Command::PowerUp, powerUp, sizeof(powerUp));
|
||||||
|
!pu) {
|
||||||
|
return std::unexpected(Si4684Error::PowerUpFailed);
|
||||||
|
}
|
||||||
|
|
||||||
|
vTaskDelay(pdMS_TO_TICKS(1));
|
||||||
|
|
||||||
|
if (auto li = writeCommand(Command::LoadInit, nullptr, 0U); !li) {
|
||||||
|
return std::unexpected(Si4684Error::PatchLoadFailed);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (auto patch = hostLoadBlob(patch_, 124U); !patch) {
|
||||||
|
return std::unexpected(Si4684Error::PatchLoadFailed);
|
||||||
|
}
|
||||||
|
|
||||||
|
vTaskDelay(pdMS_TO_TICKS(4));
|
||||||
|
|
||||||
|
if (auto li2 = writeCommand(Command::LoadInit, nullptr, 0U); !li2) {
|
||||||
|
return std::unexpected(Si4684Error::ImageLoadFailed);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (auto fw = hostLoadBlob(image, 2044U); !fw) {
|
||||||
|
return std::unexpected(Si4684Error::ImageLoadFailed);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (auto bootCmd = writeCommand(Command::BootCmd, nullptr, 0U); !bootCmd) {
|
||||||
|
return std::unexpected(Si4684Error::BootFailed);
|
||||||
|
}
|
||||||
|
|
||||||
|
booted_ = true;
|
||||||
|
loadedBand_ = band;
|
||||||
|
|
||||||
|
if (auto cfg = configureAfterBoot(band); !cfg) {
|
||||||
|
booted_ = false;
|
||||||
|
return cfg;
|
||||||
|
}
|
||||||
|
|
||||||
|
ESP_LOGI(kTag, "%s firmware booted",
|
||||||
|
band == Si4684Band::Fm ? "FM" : "DAB");
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
|
||||||
|
bool Si4684Driver::isBooted() const noexcept
|
||||||
|
{
|
||||||
|
return booted_;
|
||||||
|
}
|
||||||
|
|
||||||
|
Si4684Band Si4684Driver::loadedBand() const noexcept
|
||||||
|
{
|
||||||
|
return loadedBand_;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::expected<void, Si4684Error> Si4684Driver::tuneFm(
|
||||||
|
core::FrequencyKHz frequency)
|
||||||
|
{
|
||||||
|
if (auto band = ensureBand(Si4684Band::Fm); !band) {
|
||||||
|
return band;
|
||||||
|
}
|
||||||
|
const std::uint16_t chipFreq = kHzToChipFmFreq(frequency.value());
|
||||||
|
const std::uint8_t args[] = {
|
||||||
|
0x00U,
|
||||||
|
static_cast<std::uint8_t>(chipFreq & 0xFFU),
|
||||||
|
static_cast<std::uint8_t>(chipFreq >> 8),
|
||||||
|
0x00U,
|
||||||
|
0x00U,
|
||||||
|
};
|
||||||
|
if (auto cmd = writeCommand(Command::FmTuneFreq, args, sizeof(args));
|
||||||
|
!cmd) {
|
||||||
|
return std::unexpected(Si4684Error::TuneFailed);
|
||||||
|
}
|
||||||
|
if (auto stc = waitStc(); !stc) {
|
||||||
|
return stc;
|
||||||
|
}
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
|
||||||
|
std::expected<core::FrequencyKHz, Si4684Error> Si4684Driver::seekFm(
|
||||||
|
core::SeekDirection direction, SeekBandWrap wrap)
|
||||||
|
{
|
||||||
|
if (auto band = ensureBand(Si4684Band::Fm); !band) {
|
||||||
|
return std::unexpected(band.error());
|
||||||
|
}
|
||||||
|
const bool seekUp = direction == core::SeekDirection::Up;
|
||||||
|
const bool wrapBand = wrap == SeekBandWrap::Wrap;
|
||||||
|
const std::uint8_t args[] = {
|
||||||
|
0x10U,
|
||||||
|
static_cast<std::uint8_t>(((seekUp ? 1U : 0U) << 1U) | (wrapBand ? 1U : 0U)),
|
||||||
|
0x00U,
|
||||||
|
0x00U,
|
||||||
|
0x00U,
|
||||||
|
};
|
||||||
|
if (auto cmd = writeCommand(Command::FmSeekStart, args, sizeof(args));
|
||||||
|
!cmd) {
|
||||||
|
return std::unexpected(Si4684Error::TuneFailed);
|
||||||
|
}
|
||||||
|
if (auto stc = waitStc(); !stc) {
|
||||||
|
return std::unexpected(stc.error());
|
||||||
|
}
|
||||||
|
auto rsq = readFmRsq();
|
||||||
|
if (!rsq) {
|
||||||
|
return std::unexpected(rsq.error());
|
||||||
|
}
|
||||||
|
return rsq->frequency;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::expected<Si4684FmRsq, Si4684Error> Si4684Driver::readFmRsq()
|
||||||
|
{
|
||||||
|
if (auto band = ensureBand(Si4684Band::Fm); !band) {
|
||||||
|
return std::unexpected(band.error());
|
||||||
|
}
|
||||||
|
const std::uint8_t args[] = {0x00U};
|
||||||
|
if (auto cmd = writeCommand(Command::FmRsqStatus, args, sizeof(args));
|
||||||
|
!cmd) {
|
||||||
|
return std::unexpected(Si4684Error::CommandFailed);
|
||||||
|
}
|
||||||
|
std::array<std::uint8_t, 23> raw = {};
|
||||||
|
if (auto rd = readRaw(raw); !rd) {
|
||||||
|
return std::unexpected(rd.error());
|
||||||
|
}
|
||||||
|
|
||||||
|
Si4684FmRsq rsq = {};
|
||||||
|
const auto khz = chipFmFreqToKHz(readLe16(raw.data() + 6));
|
||||||
|
if (auto freq = core::FrequencyKHz::tryFromKhz(khz); freq) {
|
||||||
|
rsq.frequency = *freq;
|
||||||
|
} else {
|
||||||
|
return std::unexpected(Si4684Error::CommandFailed);
|
||||||
|
}
|
||||||
|
rsq.valid = (raw[4] & 0x01U) != 0U;
|
||||||
|
rsq.stereo = (raw[4] & 0x02U) != 0U;
|
||||||
|
rsq.rssiDbuV = static_cast<std::int8_t>(raw[8]);
|
||||||
|
rsq.snrDb = static_cast<std::int8_t>(raw[9]);
|
||||||
|
return rsq;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::expected<Si4684FmRdsStatus, Si4684Error> Si4684Driver::readFmRds()
|
||||||
|
{
|
||||||
|
if (auto band = ensureBand(Si4684Band::Fm); !band) {
|
||||||
|
return std::unexpected(band.error());
|
||||||
|
}
|
||||||
|
const std::uint8_t args[] = {0x01U};
|
||||||
|
if (auto cmd = writeCommand(Command::FmRdsStatus, args, sizeof(args));
|
||||||
|
!cmd) {
|
||||||
|
return std::unexpected(Si4684Error::CommandFailed);
|
||||||
|
}
|
||||||
|
std::array<std::uint8_t, 21> raw = {};
|
||||||
|
if (auto rd = readRaw(raw); !rd) {
|
||||||
|
return std::unexpected(rd.error());
|
||||||
|
}
|
||||||
|
|
||||||
|
Si4684FmRdsStatus rds = {};
|
||||||
|
rds.received = (raw[4] & 0x01U) != 0U;
|
||||||
|
rds.blockA = readLe16(raw.data() + 12);
|
||||||
|
rds.blockB = readLe16(raw.data() + 14);
|
||||||
|
rds.blockC = readLe16(raw.data() + 16);
|
||||||
|
rds.blockD = readLe16(raw.data() + 18);
|
||||||
|
return rds;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::expected<void, Si4684Error> Si4684Driver::installDefaultDabFrequencyPlan()
|
||||||
|
{
|
||||||
|
if (auto band = ensureBand(Si4684Band::Dab); !band) {
|
||||||
|
return band;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::array<std::uint8_t, 4U + kDefaultDabFrequencyKhz.size() * 4U> cmd =
|
||||||
|
{};
|
||||||
|
cmd[0] = static_cast<std::uint8_t>(Command::DabSetFreqList);
|
||||||
|
cmd[1] = static_cast<std::uint8_t>(kDefaultDabFrequencyKhz.size());
|
||||||
|
cmd[2] = 0x00U;
|
||||||
|
cmd[3] = 0x00U;
|
||||||
|
for (std::size_t i = 0; i < kDefaultDabFrequencyKhz.size(); ++i) {
|
||||||
|
const std::uint32_t hz = kDefaultDabFrequencyKhz[i];
|
||||||
|
const std::size_t off = 4U + i * 4U;
|
||||||
|
cmd[off] = static_cast<std::uint8_t>(hz & 0xFFU);
|
||||||
|
cmd[off + 1] = static_cast<std::uint8_t>((hz >> 8) & 0xFFU);
|
||||||
|
cmd[off + 2] = static_cast<std::uint8_t>((hz >> 16) & 0xFFU);
|
||||||
|
cmd[off + 3] = static_cast<std::uint8_t>(hz >> 24);
|
||||||
|
}
|
||||||
|
return sendCommand(cmd);
|
||||||
|
}
|
||||||
|
|
||||||
|
std::expected<void, Si4684Error> Si4684Driver::tuneDab(std::uint8_t freqIndex)
|
||||||
|
{
|
||||||
|
if (auto band = ensureBand(Si4684Band::Dab); !band) {
|
||||||
|
return band;
|
||||||
|
}
|
||||||
|
if (freqIndex >= kDefaultDabFrequencyKhz.size()) {
|
||||||
|
return std::unexpected(Si4684Error::TuneFailed);
|
||||||
|
}
|
||||||
|
const std::uint8_t args[] = {0x00U, freqIndex, 0x00U, 0x00U, 0x00U};
|
||||||
|
if (auto cmd = writeCommand(Command::DabTuneFreq, args, sizeof(args));
|
||||||
|
!cmd) {
|
||||||
|
return std::unexpected(Si4684Error::TuneFailed);
|
||||||
|
}
|
||||||
|
if (auto stc = waitStc(); !stc) {
|
||||||
|
return stc;
|
||||||
|
}
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
|
||||||
|
std::expected<Si4684DabDigRadStatus, Si4684Error>
|
||||||
|
Si4684Driver::readDabDigRadStatus()
|
||||||
|
{
|
||||||
|
if (auto band = ensureBand(Si4684Band::Dab); !band) {
|
||||||
|
return std::unexpected(band.error());
|
||||||
|
}
|
||||||
|
const std::uint8_t args[] = {0x01U};
|
||||||
|
if (auto cmd =
|
||||||
|
writeCommand(Command::DabDigRadStatus, args, sizeof(args));
|
||||||
|
!cmd) {
|
||||||
|
return std::unexpected(Si4684Error::CommandFailed);
|
||||||
|
}
|
||||||
|
std::array<std::uint8_t, 20> raw = {};
|
||||||
|
if (auto rd = readRaw(raw); !rd) {
|
||||||
|
return std::unexpected(rd.error());
|
||||||
|
}
|
||||||
|
|
||||||
|
Si4684DabDigRadStatus status = {};
|
||||||
|
status.ficQuality = raw[9];
|
||||||
|
status.cnrDb = raw[10];
|
||||||
|
status.acquired = (raw[4] & 0x08U) != 0U;
|
||||||
|
status.valid = status.ficQuality > 0U;
|
||||||
|
return status;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::expected<Si4684DabEventStatus, Si4684Error>
|
||||||
|
Si4684Driver::readDabEventStatus()
|
||||||
|
{
|
||||||
|
if (auto band = ensureBand(Si4684Band::Dab); !band) {
|
||||||
|
return std::unexpected(band.error());
|
||||||
|
}
|
||||||
|
const std::uint8_t args[] = {0x00U};
|
||||||
|
if (auto cmd =
|
||||||
|
writeCommand(Command::DabGetEventStatus, args, sizeof(args));
|
||||||
|
!cmd) {
|
||||||
|
return std::unexpected(Si4684Error::CommandFailed);
|
||||||
|
}
|
||||||
|
std::array<std::uint8_t, 9> raw = {};
|
||||||
|
if (auto rd = readRaw(raw); !rd) {
|
||||||
|
return std::unexpected(rd.error());
|
||||||
|
}
|
||||||
|
|
||||||
|
Si4684DabEventStatus events = {};
|
||||||
|
events.serviceListReady = (raw[4] & 0x01U) != 0U;
|
||||||
|
events.reconfig = (raw[4] & 0x02U) != 0U;
|
||||||
|
return events;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::expected<std::vector<Si4684DabService>, Si4684Error>
|
||||||
|
Si4684Driver::fetchDabServiceList()
|
||||||
|
{
|
||||||
|
if (auto band = ensureBand(Si4684Band::Dab); !band) {
|
||||||
|
return std::unexpected(band.error());
|
||||||
|
}
|
||||||
|
|
||||||
|
const std::uint8_t args[] = {0x00U};
|
||||||
|
if (auto cmd = writeCommand(Command::GetDigitalServiceList, args,
|
||||||
|
sizeof(args));
|
||||||
|
!cmd) {
|
||||||
|
return std::unexpected(Si4684Error::CommandFailed);
|
||||||
|
}
|
||||||
|
|
||||||
|
std::array<std::uint8_t, 9> header = {};
|
||||||
|
if (auto rd = readRaw(header); !rd) {
|
||||||
|
return std::unexpected(rd.error());
|
||||||
|
}
|
||||||
|
|
||||||
|
const std::uint16_t payloadSize = readLe16(header.data() + 5);
|
||||||
|
if (payloadSize == 0U || payloadSize + 6U > kSpiBufferSize) {
|
||||||
|
return std::unexpected(Si4684Error::ReplyTooShort);
|
||||||
|
}
|
||||||
|
|
||||||
|
std::vector<std::uint8_t> body(payloadSize + 6U, 0U);
|
||||||
|
if (auto rd = readRaw(body); !rd) {
|
||||||
|
return std::unexpected(rd.error());
|
||||||
|
}
|
||||||
|
|
||||||
|
const std::uint8_t serviceCount = body[9];
|
||||||
|
std::vector<Si4684DabService> services;
|
||||||
|
services.reserve(serviceCount);
|
||||||
|
|
||||||
|
std::size_t offset = 13U;
|
||||||
|
for (std::uint8_t i = 0; i < serviceCount; ++i) {
|
||||||
|
if (offset + 24U > body.size()) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
Si4684DabService entry = {};
|
||||||
|
entry.serviceId = readLe32(body.data() + offset);
|
||||||
|
offset += 4U;
|
||||||
|
entry.serviceType = body[offset] & 0x3FU;
|
||||||
|
const std::uint8_t componentCount = body[offset + 1] & 0x0FU;
|
||||||
|
offset += 4U;
|
||||||
|
std::memcpy(entry.label.data(), body.data() + offset, 16U);
|
||||||
|
entry.label[16] = '\0';
|
||||||
|
offset += 16U;
|
||||||
|
|
||||||
|
if (componentCount > 0U && offset + 4U <= body.size()) {
|
||||||
|
entry.componentId = readLe32(body.data() + offset);
|
||||||
|
offset += 4U;
|
||||||
|
if (offset < body.size()) {
|
||||||
|
++offset;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
services.push_back(entry);
|
||||||
|
}
|
||||||
|
return services;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::expected<void, Si4684Error> Si4684Driver::startDabService(
|
||||||
|
std::uint32_t serviceId,
|
||||||
|
std::uint32_t componentId,
|
||||||
|
Si4684DigitalServiceType type)
|
||||||
|
{
|
||||||
|
if (auto band = ensureBand(Si4684Band::Dab); !band) {
|
||||||
|
return band;
|
||||||
|
}
|
||||||
|
const std::uint8_t args[] = {
|
||||||
|
static_cast<std::uint8_t>(type),
|
||||||
|
0x00U,
|
||||||
|
0x00U,
|
||||||
|
static_cast<std::uint8_t>(serviceId & 0xFFU),
|
||||||
|
static_cast<std::uint8_t>((serviceId >> 8) & 0xFFU),
|
||||||
|
static_cast<std::uint8_t>((serviceId >> 16) & 0xFFU),
|
||||||
|
static_cast<std::uint8_t>(serviceId >> 24),
|
||||||
|
static_cast<std::uint8_t>(componentId & 0xFFU),
|
||||||
|
static_cast<std::uint8_t>((componentId >> 8) & 0xFFU),
|
||||||
|
static_cast<std::uint8_t>((componentId >> 16) & 0xFFU),
|
||||||
|
static_cast<std::uint8_t>(componentId >> 24),
|
||||||
|
};
|
||||||
|
if (auto cmd =
|
||||||
|
writeCommand(Command::StartDigitalService, args, sizeof(args));
|
||||||
|
!cmd) {
|
||||||
|
return std::unexpected(Si4684Error::CommandFailed);
|
||||||
|
}
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace si4684
|
||||||
@@ -0,0 +1,89 @@
|
|||||||
|
/**
|
||||||
|
* @file Si4684EmbeddedImages.cpp
|
||||||
|
* @brief Si4684EmbeddedImages implementation.
|
||||||
|
*
|
||||||
|
* DigiRadio firmware — https://github.com/manvalan/DigiRadio
|
||||||
|
*
|
||||||
|
* Copyright 2026 Michele Bigi
|
||||||
|
* SPDX-License-Identifier: Apache-2.0
|
||||||
|
*
|
||||||
|
* @author Michele Bigi
|
||||||
|
* @date 2026-07-06
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include "si4684/Si4684EmbeddedImages.hpp"
|
||||||
|
|
||||||
|
#include <cstddef>
|
||||||
|
#include <cstdint>
|
||||||
|
|
||||||
|
/** @cond linker_symbols */
|
||||||
|
extern "C" {
|
||||||
|
extern const std::uint8_t rom_patch_016_bin_start[]
|
||||||
|
asm("_binary_rom_patch_016_bin_start");
|
||||||
|
extern const std::uint8_t rom_patch_016_bin_end[]
|
||||||
|
asm("_binary_rom_patch_016_bin_end");
|
||||||
|
extern const std::uint8_t dab_firmware_bin_start[]
|
||||||
|
asm("_binary_dab_firmware_bin_start");
|
||||||
|
extern const std::uint8_t dab_firmware_bin_end[]
|
||||||
|
asm("_binary_dab_firmware_bin_end");
|
||||||
|
extern const std::uint8_t fm_firmware_bin_start[]
|
||||||
|
asm("_binary_fm_firmware_bin_start");
|
||||||
|
extern const std::uint8_t fm_firmware_bin_end[]
|
||||||
|
asm("_binary_fm_firmware_bin_end");
|
||||||
|
}
|
||||||
|
/** @endcond */
|
||||||
|
|
||||||
|
namespace si4684 {
|
||||||
|
|
||||||
|
namespace {
|
||||||
|
|
||||||
|
const std::byte* asBytes(const std::uint8_t* ptr)
|
||||||
|
{
|
||||||
|
return reinterpret_cast<const std::byte*>(ptr);
|
||||||
|
}
|
||||||
|
|
||||||
|
std::size_t embeddedSize(const std::uint8_t* start, const std::uint8_t* end)
|
||||||
|
{
|
||||||
|
return static_cast<std::size_t>(end - start);
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace
|
||||||
|
|
||||||
|
Si4684EmbeddedImages::Si4684EmbeddedImages()
|
||||||
|
: patch_(asBytes(rom_patch_016_bin_start),
|
||||||
|
embeddedSize(rom_patch_016_bin_start, rom_patch_016_bin_end))
|
||||||
|
, dab_(asBytes(dab_firmware_bin_start),
|
||||||
|
embeddedSize(dab_firmware_bin_start, dab_firmware_bin_end))
|
||||||
|
, fm_(asBytes(fm_firmware_bin_start),
|
||||||
|
embeddedSize(fm_firmware_bin_start, fm_firmware_bin_end))
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
const core::IFirmwareBlobReader& Si4684EmbeddedImages::romPatch() const noexcept
|
||||||
|
{
|
||||||
|
return patch_;
|
||||||
|
}
|
||||||
|
|
||||||
|
const core::IFirmwareBlobReader& Si4684EmbeddedImages::dabFirmware() const noexcept
|
||||||
|
{
|
||||||
|
return dab_;
|
||||||
|
}
|
||||||
|
|
||||||
|
const core::IFirmwareBlobReader& Si4684EmbeddedImages::fmFirmware() const noexcept
|
||||||
|
{
|
||||||
|
return fm_;
|
||||||
|
}
|
||||||
|
|
||||||
|
const core::IFirmwareBlobReader& Si4684EmbeddedImages::applicationImage(
|
||||||
|
Si4684Band band) const noexcept
|
||||||
|
{
|
||||||
|
switch (band) {
|
||||||
|
case Si4684Band::Dab:
|
||||||
|
return dab_;
|
||||||
|
case Si4684Band::Fm:
|
||||||
|
return fm_;
|
||||||
|
}
|
||||||
|
return dab_;
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace si4684
|
||||||
@@ -0,0 +1,186 @@
|
|||||||
|
/**
|
||||||
|
* @file Si4684Tuner.cpp
|
||||||
|
* @brief Si4684Tuner implementation.
|
||||||
|
*
|
||||||
|
* DigiRadio firmware — https://github.com/manvalan/DigiRadio
|
||||||
|
*
|
||||||
|
* Copyright 2026 Michele Bigi
|
||||||
|
* SPDX-License-Identifier: Apache-2.0
|
||||||
|
*
|
||||||
|
* @author Michele Bigi
|
||||||
|
* @date 2026-07-06
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include "si4684/Si4684Tuner.hpp"
|
||||||
|
|
||||||
|
#include "si4684/Si4684Band.hpp"
|
||||||
|
|
||||||
|
namespace si4684 {
|
||||||
|
|
||||||
|
namespace {
|
||||||
|
|
||||||
|
[[nodiscard]] core::FrequencyKHz defaultFmFrequency()
|
||||||
|
{
|
||||||
|
return *core::FrequencyKHz::tryFromKhz(101500U);
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace
|
||||||
|
|
||||||
|
Si4684Tuner::Si4684Tuner(Si4684Driver& driver)
|
||||||
|
: driver_(driver)
|
||||||
|
, dabIndex_(0U)
|
||||||
|
, fmFrequency_(defaultFmFrequency())
|
||||||
|
, volume_(40U)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
core::TunerError Si4684Tuner::mapError(Si4684Error error) noexcept
|
||||||
|
{
|
||||||
|
switch (error) {
|
||||||
|
case Si4684Error::NotBooted:
|
||||||
|
return core::TunerError::NotBooted;
|
||||||
|
case Si4684Error::WrongBand:
|
||||||
|
return core::TunerError::WrongBand;
|
||||||
|
case Si4684Error::TuneFailed:
|
||||||
|
case Si4684Error::StcTimeout:
|
||||||
|
return core::TunerError::TuneFailed;
|
||||||
|
default:
|
||||||
|
return core::TunerError::HardwareFailed;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
std::expected<void, core::TunerError> Si4684Tuner::boot(core::TunerBand band)
|
||||||
|
{
|
||||||
|
const Si4684Band hwBand =
|
||||||
|
(band == core::TunerBand::Fm) ? Si4684Band::Fm : Si4684Band::Dab;
|
||||||
|
if (auto result = driver_.boot(hwBand); !result) {
|
||||||
|
return std::unexpected(mapError(result.error()));
|
||||||
|
}
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
|
||||||
|
std::expected<core::TunerBand, core::TunerError> Si4684Tuner::currentBand() const
|
||||||
|
{
|
||||||
|
if (!driver_.isBooted()) {
|
||||||
|
return std::unexpected(core::TunerError::NotBooted);
|
||||||
|
}
|
||||||
|
return driver_.loadedBand() == Si4684Band::Fm ? core::TunerBand::Fm
|
||||||
|
: core::TunerBand::Dab;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::expected<core::TunerStatus, core::TunerError> Si4684Tuner::readStatus()
|
||||||
|
{
|
||||||
|
if (!driver_.isBooted()) {
|
||||||
|
return std::unexpected(core::TunerError::NotBooted);
|
||||||
|
}
|
||||||
|
|
||||||
|
core::TunerStatus status = {};
|
||||||
|
status.booted = true;
|
||||||
|
status.volume = volume_;
|
||||||
|
status.band = driver_.loadedBand() == Si4684Band::Fm ? core::TunerBand::Fm
|
||||||
|
: core::TunerBand::Dab;
|
||||||
|
|
||||||
|
if (status.band == core::TunerBand::Dab) {
|
||||||
|
status.dabFreqIndex = dabIndex_;
|
||||||
|
if (auto dig = driver_.readDabDigRadStatus(); dig) {
|
||||||
|
status.locked = dig->valid;
|
||||||
|
status.dabFicQuality = dig->ficQuality;
|
||||||
|
status.dabCnrDb = dig->cnrDb;
|
||||||
|
} else {
|
||||||
|
return std::unexpected(mapError(dig.error()));
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
status.fmFrequency = fmFrequency_;
|
||||||
|
if (auto rsq = driver_.readFmRsq(); rsq) {
|
||||||
|
status.locked = rsq->valid;
|
||||||
|
status.fmFrequency = rsq->frequency;
|
||||||
|
status.fmRssiDbuV = rsq->rssiDbuV;
|
||||||
|
status.fmSnrDb = rsq->snrDb;
|
||||||
|
status.fmStereo = rsq->stereo;
|
||||||
|
fmFrequency_ = rsq->frequency;
|
||||||
|
} else {
|
||||||
|
return std::unexpected(mapError(rsq.error()));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return status;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::expected<void, core::TunerError> Si4684Tuner::tuneDab(
|
||||||
|
std::uint8_t freqIndex)
|
||||||
|
{
|
||||||
|
if (auto result = driver_.tuneDab(freqIndex); !result) {
|
||||||
|
return std::unexpected(mapError(result.error()));
|
||||||
|
}
|
||||||
|
dabIndex_ = freqIndex;
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
|
||||||
|
std::expected<void, core::TunerError> Si4684Tuner::tuneFm(
|
||||||
|
core::FrequencyKHz frequency)
|
||||||
|
{
|
||||||
|
if (auto result = driver_.tuneFm(frequency); !result) {
|
||||||
|
return std::unexpected(mapError(result.error()));
|
||||||
|
}
|
||||||
|
fmFrequency_ = frequency;
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
|
||||||
|
std::expected<core::FrequencyKHz, core::TunerError> Si4684Tuner::seekFm(
|
||||||
|
core::SeekDirection direction)
|
||||||
|
{
|
||||||
|
if (auto result = driver_.seekFm(direction, SeekBandWrap::Wrap); !result) {
|
||||||
|
return std::unexpected(mapError(result.error()));
|
||||||
|
}
|
||||||
|
fmFrequency_ = *result;
|
||||||
|
return *result;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::expected<std::vector<core::TunerServiceEntry>, core::TunerError>
|
||||||
|
Si4684Tuner::listDabServices()
|
||||||
|
{
|
||||||
|
if (auto events = driver_.readDabEventStatus(); events) {
|
||||||
|
if (!events->serviceListReady) {
|
||||||
|
return std::unexpected(core::TunerError::ServiceListEmpty);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
return std::unexpected(mapError(events.error()));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (auto list = driver_.fetchDabServiceList(); list) {
|
||||||
|
std::vector<core::TunerServiceEntry> out;
|
||||||
|
out.reserve(list->size());
|
||||||
|
for (const auto& item : *list) {
|
||||||
|
core::TunerServiceEntry entry = {};
|
||||||
|
entry.serviceId = item.serviceId;
|
||||||
|
entry.componentId = item.componentId;
|
||||||
|
entry.label = item.label;
|
||||||
|
out.push_back(entry);
|
||||||
|
}
|
||||||
|
if (out.empty()) {
|
||||||
|
return std::unexpected(core::TunerError::ServiceListEmpty);
|
||||||
|
}
|
||||||
|
return out;
|
||||||
|
}
|
||||||
|
return std::unexpected(mapError(list.error()));
|
||||||
|
}
|
||||||
|
|
||||||
|
std::expected<void, core::TunerError> Si4684Tuner::playDabService(
|
||||||
|
std::uint32_t serviceId,
|
||||||
|
std::uint32_t componentId)
|
||||||
|
{
|
||||||
|
if (auto result = driver_.startDabService(serviceId, componentId); !result) {
|
||||||
|
return std::unexpected(mapError(result.error()));
|
||||||
|
}
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
|
||||||
|
std::expected<void, core::TunerError> Si4684Tuner::setVolume(std::uint8_t level)
|
||||||
|
{
|
||||||
|
if (auto result = driver_.setVolume(level); !result) {
|
||||||
|
return std::unexpected(mapError(result.error()));
|
||||||
|
}
|
||||||
|
volume_ = level & 0x3FU;
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace si4684
|
||||||
@@ -1,33 +0,0 @@
|
|||||||
/**
|
|
||||||
* @file component_stub.cpp
|
|
||||||
* @brief Si4684 driver component placeholder (Slice 4).
|
|
||||||
*
|
|
||||||
* DigiRadio firmware — https://github.com/manvalan/DigiRadio
|
|
||||||
*
|
|
||||||
* Copyright 2026 Michele Bigi
|
|
||||||
* SPDX-License-Identifier: Apache-2.0
|
|
||||||
*
|
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
||||||
* you may not use this file except in compliance with the License.
|
|
||||||
* You may obtain a copy of the License at
|
|
||||||
* http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
*
|
|
||||||
* @author Michele Bigi
|
|
||||||
* @date 2026-07-06
|
|
||||||
*/
|
|
||||||
|
|
||||||
namespace si4684::detail {
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief si4684ComponentLinked — ensures the driver component links.
|
|
||||||
*
|
|
||||||
* @dname si4684ComponentLinked
|
|
||||||
* @return n/a (type)
|
|
||||||
* @pubstate n/a
|
|
||||||
*
|
|
||||||
* @author Michele Bigi
|
|
||||||
* @date 2026-07-06
|
|
||||||
*/
|
|
||||||
void si4684ComponentLinked() noexcept {}
|
|
||||||
|
|
||||||
} // namespace si4684::detail
|
|
||||||
@@ -7,7 +7,7 @@ idf_component_register(
|
|||||||
"src/NetBootstrap.cpp"
|
"src/NetBootstrap.cpp"
|
||||||
INCLUDE_DIRS "include"
|
INCLUDE_DIRS "include"
|
||||||
EMBED_FILES "www/index.html.gz"
|
EMBED_FILES "www/index.html.gz"
|
||||||
REQUIRES core esp_wifi esp_netif esp_event nvs_flash esp_http_server
|
REQUIRES core esp_wifi esp_netif esp_event nvs_flash esp_http_server tuner
|
||||||
)
|
)
|
||||||
|
|
||||||
target_compile_features(${COMPONENT_LIB} PUBLIC cxx_std_23)
|
target_compile_features(${COMPONENT_LIB} PUBLIC cxx_std_23)
|
||||||
|
|||||||
@@ -27,6 +27,10 @@
|
|||||||
#include <expected>
|
#include <expected>
|
||||||
#include <optional>
|
#include <optional>
|
||||||
|
|
||||||
|
namespace tuner {
|
||||||
|
class TunerService;
|
||||||
|
} // namespace tuner
|
||||||
|
|
||||||
namespace net {
|
namespace net {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -47,6 +51,7 @@ public:
|
|||||||
*
|
*
|
||||||
* @dname start
|
* @dname start
|
||||||
* @param store Secure store consulted for saved STA credentials.
|
* @param store Secure store consulted for saved STA credentials.
|
||||||
|
* @param tuner Tuner service exposed by the HTTP API.
|
||||||
* @return NetBootstrap on success, or a NetError.
|
* @return NetBootstrap on success, or a NetError.
|
||||||
* @pubstate none
|
* @pubstate none
|
||||||
*
|
*
|
||||||
@@ -54,7 +59,7 @@ public:
|
|||||||
* @date 2026-07-06
|
* @date 2026-07-06
|
||||||
*/
|
*/
|
||||||
[[nodiscard]] static std::expected<NetBootstrap, NetError>
|
[[nodiscard]] static std::expected<NetBootstrap, NetError>
|
||||||
start(core::ISecureStore& store);
|
start(core::ISecureStore& store, tuner::TunerService& tuner);
|
||||||
|
|
||||||
NetBootstrap(const NetBootstrap&) = delete;
|
NetBootstrap(const NetBootstrap&) = delete;
|
||||||
NetBootstrap& operator=(const NetBootstrap&) = delete;
|
NetBootstrap& operator=(const NetBootstrap&) = delete;
|
||||||
|
|||||||
@@ -23,10 +23,32 @@
|
|||||||
|
|
||||||
#include <expected>
|
#include <expected>
|
||||||
|
|
||||||
|
struct httpd_req;
|
||||||
|
|
||||||
|
namespace tuner {
|
||||||
|
class TunerService;
|
||||||
|
} // namespace tuner
|
||||||
|
|
||||||
struct httpd_handle;
|
struct httpd_handle;
|
||||||
|
|
||||||
namespace net {
|
namespace net {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief HttpRouteContext — dependencies injected into HTTP handlers.
|
||||||
|
*
|
||||||
|
* @dname HttpRouteContext
|
||||||
|
* @return n/a (type)
|
||||||
|
* @pubstate Borrows store and tuner for the lifetime of SetupWebServer.
|
||||||
|
* Passed as esp_http_server user_ctx (no file-scope globals).
|
||||||
|
*
|
||||||
|
* @author Michele Bigi
|
||||||
|
* @date 2026-07-06
|
||||||
|
*/
|
||||||
|
struct HttpRouteContext {
|
||||||
|
core::ISecureStore* store; ///< Secure store for Wi-Fi provisioning.
|
||||||
|
tuner::TunerService* tuner; ///< Tuner service for tuner REST routes.
|
||||||
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @brief SetupWebServer — setup UI, health, and Wi-Fi provisioning API.
|
* @brief SetupWebServer — setup UI, health, and Wi-Fi provisioning API.
|
||||||
*
|
*
|
||||||
@@ -96,19 +118,23 @@ public:
|
|||||||
* @dname start
|
* @dname start
|
||||||
* @param store Secure store for POST /api/wifi persistence.
|
* @param store Secure store for POST /api/wifi persistence.
|
||||||
* @param netState Active network phase exposed to handlers.
|
* @param netState Active network phase exposed to handlers.
|
||||||
|
* @param tuner Tuner service for the tuner REST routes.
|
||||||
* @return Ok on success, or NetError::HttpServerStartFailed.
|
* @return Ok on success, or NetError::HttpServerStartFailed.
|
||||||
* @pubstate writes server_, store_, and netState_ on success.
|
* @pubstate writes server_, store_, netState_, and tuner_ on success.
|
||||||
*
|
*
|
||||||
* @author Michele Bigi
|
* @author Michele Bigi
|
||||||
* @date 2026-07-06
|
* @date 2026-07-06
|
||||||
*/
|
*/
|
||||||
[[nodiscard]] std::expected<void, NetError> start(core::ISecureStore& store,
|
[[nodiscard]] std::expected<void, NetError> start(core::ISecureStore& store,
|
||||||
NetState netState);
|
NetState netState,
|
||||||
|
tuner::TunerService& tuner);
|
||||||
|
|
||||||
private:
|
private:
|
||||||
httpd_handle* server_;
|
httpd_handle* server_;
|
||||||
core::ISecureStore* store_;
|
core::ISecureStore* store_;
|
||||||
NetState netState_;
|
NetState netState_;
|
||||||
|
tuner::TunerService* tuner_;
|
||||||
|
HttpRouteContext routeContext_;
|
||||||
};
|
};
|
||||||
|
|
||||||
} // namespace net
|
} // namespace net
|
||||||
|
|||||||
@@ -23,6 +23,7 @@
|
|||||||
#include "esp_netif.h"
|
#include "esp_netif.h"
|
||||||
#include "esp_wifi.h"
|
#include "esp_wifi.h"
|
||||||
#include "nvs_flash.h"
|
#include "nvs_flash.h"
|
||||||
|
#include "tuner/TunerService.hpp"
|
||||||
|
|
||||||
namespace net {
|
namespace net {
|
||||||
|
|
||||||
@@ -97,7 +98,7 @@ constexpr char kTag[] = "NetBootstrap";
|
|||||||
* @date 2026-07-06
|
* @date 2026-07-06
|
||||||
*/
|
*/
|
||||||
[[nodiscard]] std::expected<NetBootstrap, NetError>
|
[[nodiscard]] std::expected<NetBootstrap, NetError>
|
||||||
startSetupMode(core::ISecureStore& store)
|
startSetupMode(core::ISecureStore& store, tuner::TunerService& tuner)
|
||||||
{
|
{
|
||||||
esp_netif_create_default_wifi_ap();
|
esp_netif_create_default_wifi_ap();
|
||||||
|
|
||||||
@@ -107,7 +108,7 @@ startSetupMode(core::ISecureStore& store)
|
|||||||
}
|
}
|
||||||
|
|
||||||
SetupWebServer webServer;
|
SetupWebServer webServer;
|
||||||
if (auto webResult = webServer.start(store, NetState::SoftApSetup);
|
if (auto webResult = webServer.start(store, NetState::SoftApSetup, tuner);
|
||||||
!webResult) {
|
!webResult) {
|
||||||
return std::unexpected(webResult.error());
|
return std::unexpected(webResult.error());
|
||||||
}
|
}
|
||||||
@@ -129,7 +130,7 @@ startSetupMode(core::ISecureStore& store)
|
|||||||
* @date 2026-07-06
|
* @date 2026-07-06
|
||||||
*/
|
*/
|
||||||
[[nodiscard]] std::expected<NetBootstrap, NetError>
|
[[nodiscard]] std::expected<NetBootstrap, NetError>
|
||||||
startStaMode(core::ISecureStore& store)
|
startStaMode(core::ISecureStore& store, tuner::TunerService& tuner)
|
||||||
{
|
{
|
||||||
auto credsResult = store.loadWifiCredentials();
|
auto credsResult = store.loadWifiCredentials();
|
||||||
if (!credsResult) {
|
if (!credsResult) {
|
||||||
@@ -145,7 +146,7 @@ startStaMode(core::ISecureStore& store)
|
|||||||
}
|
}
|
||||||
|
|
||||||
SetupWebServer webServer;
|
SetupWebServer webServer;
|
||||||
if (auto webResult = webServer.start(store, NetState::StaConnected);
|
if (auto webResult = webServer.start(store, NetState::StaConnected, tuner);
|
||||||
!webResult) {
|
!webResult) {
|
||||||
return std::unexpected(webResult.error());
|
return std::unexpected(webResult.error());
|
||||||
}
|
}
|
||||||
@@ -158,7 +159,7 @@ startStaMode(core::ISecureStore& store)
|
|||||||
} // namespace
|
} // namespace
|
||||||
|
|
||||||
std::expected<NetBootstrap, NetError>
|
std::expected<NetBootstrap, NetError>
|
||||||
NetBootstrap::start(core::ISecureStore& store)
|
NetBootstrap::start(core::ISecureStore& store, tuner::TunerService& tuner)
|
||||||
{
|
{
|
||||||
if (auto platform = initPlatform(); !platform) {
|
if (auto platform = initPlatform(); !platform) {
|
||||||
return std::unexpected(platform.error());
|
return std::unexpected(platform.error());
|
||||||
@@ -169,14 +170,14 @@ NetBootstrap::start(core::ISecureStore& store)
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (store.hasWifiCredentials()) {
|
if (store.hasWifiCredentials()) {
|
||||||
auto staResult = startStaMode(store);
|
auto staResult = startStaMode(store, tuner);
|
||||||
if (staResult) {
|
if (staResult) {
|
||||||
return staResult;
|
return staResult;
|
||||||
}
|
}
|
||||||
ESP_LOGW(kTag, "STA join failed — falling back to setup SoftAP");
|
ESP_LOGW(kTag, "STA join failed — falling back to setup SoftAP");
|
||||||
}
|
}
|
||||||
|
|
||||||
return startSetupMode(store);
|
return startSetupMode(store, tuner);
|
||||||
}
|
}
|
||||||
|
|
||||||
NetBootstrap::NetBootstrap(std::optional<SoftApHost> softAp,
|
NetBootstrap::NetBootstrap(std::optional<SoftApHost> softAp,
|
||||||
|
|||||||
@@ -22,7 +22,10 @@
|
|||||||
#include "core/HealthStatus.hpp"
|
#include "core/HealthStatus.hpp"
|
||||||
#include "core/HealthStatusJson.hpp"
|
#include "core/HealthStatusJson.hpp"
|
||||||
#include "core/ParseError.hpp"
|
#include "core/ParseError.hpp"
|
||||||
|
#include "core/SeekDirection.hpp"
|
||||||
|
#include "core/TunerJson.hpp"
|
||||||
#include "core/WifiProvisionJson.hpp"
|
#include "core/WifiProvisionJson.hpp"
|
||||||
|
#include "tuner/TunerService.hpp"
|
||||||
|
|
||||||
#include "esp_http_server.h"
|
#include "esp_http_server.h"
|
||||||
#include "esp_log.h"
|
#include "esp_log.h"
|
||||||
@@ -37,17 +40,30 @@ namespace net {
|
|||||||
|
|
||||||
namespace {
|
namespace {
|
||||||
constexpr char kTag[] = "SetupWebServer";
|
constexpr char kTag[] = "SetupWebServer";
|
||||||
constexpr char kFirmwareVersion[] = "0.2.0";
|
constexpr char kFirmwareVersion[] = "0.4.0";
|
||||||
constexpr unsigned kRebootDelaySec = 3;
|
constexpr unsigned kRebootDelaySec = 3;
|
||||||
|
|
||||||
SetupWebServer* gActiveServer = nullptr;
|
|
||||||
core::ISecureStore* gStore = nullptr;
|
|
||||||
|
|
||||||
extern const uint8_t www_index_html_gz_start[] asm(
|
extern const uint8_t www_index_html_gz_start[] asm(
|
||||||
"_binary_www_index_html_gz_start");
|
"_binary_www_index_html_gz_start");
|
||||||
extern const uint8_t www_index_html_gz_end[] asm(
|
extern const uint8_t www_index_html_gz_end[] asm(
|
||||||
"_binary_www_index_html_gz_end");
|
"_binary_www_index_html_gz_end");
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief routeContextFrom — read handler dependencies from user_ctx.
|
||||||
|
*
|
||||||
|
* @dname routeContextFrom
|
||||||
|
* @param req HTTP request handle from esp_http_server.
|
||||||
|
* @return Route context pointer, or nullptr when unset.
|
||||||
|
* @pubstate none
|
||||||
|
*
|
||||||
|
* @author Michele Bigi
|
||||||
|
* @date 2026-07-06
|
||||||
|
*/
|
||||||
|
[[nodiscard]] HttpRouteContext* routeContextFrom(httpd_req_t* req) noexcept
|
||||||
|
{
|
||||||
|
return static_cast<HttpRouteContext*>(httpd_req_get_user_ctx(req));
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @brief rebootTask — restart after provisioning so STA mode can run.
|
* @brief rebootTask — restart after provisioning so STA mode can run.
|
||||||
*
|
*
|
||||||
@@ -91,6 +107,40 @@ void rebootTask(void* arg)
|
|||||||
return "parse_error";
|
return "parse_error";
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[[nodiscard]] const char* tunerErrorToken(core::TunerError error) noexcept
|
||||||
|
{
|
||||||
|
switch (error) {
|
||||||
|
case core::TunerError::NotBooted:
|
||||||
|
return "not_booted";
|
||||||
|
case core::TunerError::WrongBand:
|
||||||
|
return "wrong_band";
|
||||||
|
case core::TunerError::TuneFailed:
|
||||||
|
return "tune_failed";
|
||||||
|
case core::TunerError::ServiceListEmpty:
|
||||||
|
return "service_list_empty";
|
||||||
|
case core::TunerError::InvalidInput:
|
||||||
|
return "invalid_input";
|
||||||
|
case core::TunerError::HardwareFailed:
|
||||||
|
return "hardware_failed";
|
||||||
|
}
|
||||||
|
return "tuner_error";
|
||||||
|
}
|
||||||
|
|
||||||
|
[[nodiscard]] bool readRequestBody(httpd_req_t* req, std::array<char, 512>& body)
|
||||||
|
{
|
||||||
|
int received = 0;
|
||||||
|
while (received < static_cast<int>(body.size()) - 1) {
|
||||||
|
const int chunk = httpd_req_recv(req, body.data() + received,
|
||||||
|
body.size() - 1 - received);
|
||||||
|
if (chunk <= 0) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
received += chunk;
|
||||||
|
}
|
||||||
|
body[static_cast<std::size_t>(received)] = '\0';
|
||||||
|
return received > 0;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @brief healthGetHandler — serve GET /api/health as JSON.
|
* @brief healthGetHandler — serve GET /api/health as JSON.
|
||||||
*
|
*
|
||||||
@@ -111,6 +161,208 @@ esp_err_t healthGetHandler(httpd_req_t* req)
|
|||||||
return httpd_resp_send(req, json.c_str(), json.size());
|
return httpd_resp_send(req, json.c_str(), json.size());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief tunerStatusGetHandler — serve GET /api/tuner/status as JSON.
|
||||||
|
*
|
||||||
|
* @dname tunerStatusGetHandler
|
||||||
|
* @param req HTTP request handle from esp_http_server.
|
||||||
|
* @return ESP_OK on success, or an esp_err_t error code.
|
||||||
|
* @pubstate uses route context tuner service.
|
||||||
|
*
|
||||||
|
* @author Michele Bigi
|
||||||
|
* @date 2026-07-06
|
||||||
|
*/
|
||||||
|
esp_err_t tunerStatusGetHandler(httpd_req_t* req)
|
||||||
|
{
|
||||||
|
auto* ctx = routeContextFrom(req);
|
||||||
|
if (ctx == nullptr || ctx->tuner == nullptr) {
|
||||||
|
httpd_resp_set_status(req, "503 Service Unavailable");
|
||||||
|
return httpd_resp_send(req, nullptr, 0);
|
||||||
|
}
|
||||||
|
auto status = ctx->tuner->refreshStatus();
|
||||||
|
if (!status) {
|
||||||
|
const std::string json =
|
||||||
|
core::serializeTunerErrorJson(tunerErrorToken(status.error()));
|
||||||
|
httpd_resp_set_status(req, "500 Internal Server Error");
|
||||||
|
httpd_resp_set_type(req, "application/json");
|
||||||
|
return httpd_resp_send(req, json.c_str(), json.size());
|
||||||
|
}
|
||||||
|
const std::string json = core::serializeTunerStatusJson(*status);
|
||||||
|
httpd_resp_set_type(req, "application/json");
|
||||||
|
return httpd_resp_send(req, json.c_str(), json.size());
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief tunerServicesGetHandler — serve GET /api/tuner/services as JSON.
|
||||||
|
*
|
||||||
|
* @dname tunerServicesGetHandler
|
||||||
|
* @param req HTTP request handle from esp_http_server.
|
||||||
|
* @return ESP_OK on success, or an esp_err_t error code.
|
||||||
|
* @pubstate uses route context tuner service.
|
||||||
|
*
|
||||||
|
* @author Michele Bigi
|
||||||
|
* @date 2026-07-06
|
||||||
|
*/
|
||||||
|
esp_err_t tunerServicesGetHandler(httpd_req_t* req)
|
||||||
|
{
|
||||||
|
auto* ctx = routeContextFrom(req);
|
||||||
|
if (ctx == nullptr || ctx->tuner == nullptr) {
|
||||||
|
httpd_resp_set_status(req, "503 Service Unavailable");
|
||||||
|
return httpd_resp_send(req, nullptr, 0);
|
||||||
|
}
|
||||||
|
auto services = ctx->tuner->listDabServices();
|
||||||
|
if (!services) {
|
||||||
|
const std::string json =
|
||||||
|
core::serializeTunerErrorJson(tunerErrorToken(services.error()));
|
||||||
|
httpd_resp_set_status(req, "409 Conflict");
|
||||||
|
httpd_resp_set_type(req, "application/json");
|
||||||
|
return httpd_resp_send(req, json.c_str(), json.size());
|
||||||
|
}
|
||||||
|
const std::string json = core::serializeTunerServicesJson(*services);
|
||||||
|
httpd_resp_set_type(req, "application/json");
|
||||||
|
return httpd_resp_send(req, json.c_str(), json.size());
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief tunerTunePostHandler — accept POST /api/tuner/tune JSON.
|
||||||
|
*
|
||||||
|
* @dname tunerTunePostHandler
|
||||||
|
* @param req HTTP request handle from esp_http_server.
|
||||||
|
* @return ESP_OK on success, or an esp_err_t error code.
|
||||||
|
* @pubstate uses route context tuner service.
|
||||||
|
*
|
||||||
|
* @author Michele Bigi
|
||||||
|
* @date 2026-07-06
|
||||||
|
*/
|
||||||
|
esp_err_t tunerTunePostHandler(httpd_req_t* req)
|
||||||
|
{
|
||||||
|
auto* ctx = routeContextFrom(req);
|
||||||
|
if (ctx == nullptr || ctx->tuner == nullptr) {
|
||||||
|
httpd_resp_set_status(req, "503 Service Unavailable");
|
||||||
|
return httpd_resp_send(req, nullptr, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
std::array<char, 512> body{};
|
||||||
|
if (!readRequestBody(req, body)) {
|
||||||
|
httpd_resp_set_status(req, "400 Bad Request");
|
||||||
|
return httpd_resp_send(req, nullptr, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
const auto parsed =
|
||||||
|
core::parseTunerTuneJson(std::string_view(body.data()));
|
||||||
|
if (!parsed) {
|
||||||
|
const std::string json = core::serializeTunerErrorJson("invalid_json");
|
||||||
|
httpd_resp_set_status(req, "400 Bad Request");
|
||||||
|
httpd_resp_set_type(req, "application/json");
|
||||||
|
return httpd_resp_send(req, json.c_str(), json.size());
|
||||||
|
}
|
||||||
|
|
||||||
|
std::expected<void, core::TunerError> result = std::unexpected(
|
||||||
|
core::TunerError::InvalidInput);
|
||||||
|
if (parsed->band == core::TunerBand::Dab) {
|
||||||
|
result = ctx->tuner->tuneDab(parsed->dabFreqIndex);
|
||||||
|
} else if (parsed->fmFrequency) {
|
||||||
|
result = ctx->tuner->tuneFm(*parsed->fmFrequency);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!result) {
|
||||||
|
const std::string json =
|
||||||
|
core::serializeTunerErrorJson(tunerErrorToken(result.error()));
|
||||||
|
httpd_resp_set_status(req, "409 Conflict");
|
||||||
|
httpd_resp_set_type(req, "application/json");
|
||||||
|
return httpd_resp_send(req, json.c_str(), json.size());
|
||||||
|
}
|
||||||
|
|
||||||
|
auto status = ctx->tuner->refreshStatus();
|
||||||
|
const std::string json = status
|
||||||
|
? core::serializeTunerStatusJson(*status)
|
||||||
|
: std::string("{\"status\":\"ok\"}");
|
||||||
|
httpd_resp_set_type(req, "application/json");
|
||||||
|
return httpd_resp_send(req, json.c_str(), json.size());
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief tunerPlayPostHandler — accept POST /api/tuner/play JSON.
|
||||||
|
*
|
||||||
|
* @dname tunerPlayPostHandler
|
||||||
|
* @param req HTTP request handle from esp_http_server.
|
||||||
|
* @return ESP_OK on success, or an esp_err_t error code.
|
||||||
|
* @pubstate uses route context tuner service.
|
||||||
|
*
|
||||||
|
* @author Michele Bigi
|
||||||
|
* @date 2026-07-06
|
||||||
|
*/
|
||||||
|
esp_err_t tunerPlayPostHandler(httpd_req_t* req)
|
||||||
|
{
|
||||||
|
auto* ctx = routeContextFrom(req);
|
||||||
|
if (ctx == nullptr || ctx->tuner == nullptr) {
|
||||||
|
httpd_resp_set_status(req, "503 Service Unavailable");
|
||||||
|
return httpd_resp_send(req, nullptr, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
std::array<char, 512> body{};
|
||||||
|
if (!readRequestBody(req, body)) {
|
||||||
|
httpd_resp_set_status(req, "400 Bad Request");
|
||||||
|
return httpd_resp_send(req, nullptr, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
const auto parsed =
|
||||||
|
core::parseTunerPlayJson(std::string_view(body.data()));
|
||||||
|
if (!parsed) {
|
||||||
|
const std::string json = core::serializeTunerErrorJson("invalid_json");
|
||||||
|
httpd_resp_set_status(req, "400 Bad Request");
|
||||||
|
httpd_resp_set_type(req, "application/json");
|
||||||
|
return httpd_resp_send(req, json.c_str(), json.size());
|
||||||
|
}
|
||||||
|
|
||||||
|
if (auto play = ctx->tuner->playDabService(parsed->serviceId,
|
||||||
|
parsed->componentId);
|
||||||
|
!play) {
|
||||||
|
const std::string json =
|
||||||
|
core::serializeTunerErrorJson(tunerErrorToken(play.error()));
|
||||||
|
httpd_resp_set_status(req, "409 Conflict");
|
||||||
|
httpd_resp_set_type(req, "application/json");
|
||||||
|
return httpd_resp_send(req, json.c_str(), json.size());
|
||||||
|
}
|
||||||
|
|
||||||
|
httpd_resp_set_type(req, "application/json");
|
||||||
|
return httpd_resp_send(req, "{\"status\":\"playing\"}", 20);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief tunerSeekPostHandler — accept POST /api/tuner/seek (FM up).
|
||||||
|
*
|
||||||
|
* @dname tunerSeekPostHandler
|
||||||
|
* @param req HTTP request handle from esp_http_server.
|
||||||
|
* @return ESP_OK on success, or an esp_err_t error code.
|
||||||
|
* @pubstate uses route context tuner service with SeekDirection::Up.
|
||||||
|
*
|
||||||
|
* @author Michele Bigi
|
||||||
|
* @date 2026-07-06
|
||||||
|
*/
|
||||||
|
esp_err_t tunerSeekPostHandler(httpd_req_t* req)
|
||||||
|
{
|
||||||
|
auto* ctx = routeContextFrom(req);
|
||||||
|
if (ctx == nullptr || ctx->tuner == nullptr) {
|
||||||
|
httpd_resp_set_status(req, "503 Service Unavailable");
|
||||||
|
return httpd_resp_send(req, nullptr, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
auto freq = ctx->tuner->seekFm(core::SeekDirection::Up);
|
||||||
|
if (freq) {
|
||||||
|
const std::string json = std::string("{\"frequency_khz\":")
|
||||||
|
+ std::to_string(freq->value()) + "}";
|
||||||
|
httpd_resp_set_type(req, "application/json");
|
||||||
|
return httpd_resp_send(req, json.c_str(), json.size());
|
||||||
|
}
|
||||||
|
|
||||||
|
const std::string json =
|
||||||
|
core::serializeTunerErrorJson(tunerErrorToken(freq.error()));
|
||||||
|
httpd_resp_set_status(req, "409 Conflict");
|
||||||
|
httpd_resp_set_type(req, "application/json");
|
||||||
|
return httpd_resp_send(req, json.c_str(), json.size());
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @brief indexGetHandler — serve gzipped setup page from flash.
|
* @brief indexGetHandler — serve gzipped setup page from flash.
|
||||||
*
|
*
|
||||||
@@ -139,14 +391,15 @@ esp_err_t indexGetHandler(httpd_req_t* req)
|
|||||||
* @dname wifiPostHandler
|
* @dname wifiPostHandler
|
||||||
* @param req HTTP request handle from esp_http_server.
|
* @param req HTTP request handle from esp_http_server.
|
||||||
* @return ESP_OK on success, or an esp_err_t error code.
|
* @return ESP_OK on success, or an esp_err_t error code.
|
||||||
* @pubstate uses gActiveServer->store_ for persistence.
|
* @pubstate uses route context secure store for persistence.
|
||||||
*
|
*
|
||||||
* @author Michele Bigi
|
* @author Michele Bigi
|
||||||
* @date 2026-07-06
|
* @date 2026-07-06
|
||||||
*/
|
*/
|
||||||
esp_err_t wifiPostHandler(httpd_req_t* req)
|
esp_err_t wifiPostHandler(httpd_req_t* req)
|
||||||
{
|
{
|
||||||
if (gStore == nullptr) {
|
auto* ctx = routeContextFrom(req);
|
||||||
|
if (ctx == nullptr || ctx->store == nullptr) {
|
||||||
httpd_resp_set_status(req, "500 Internal Server Error");
|
httpd_resp_set_status(req, "500 Internal Server Error");
|
||||||
return httpd_resp_send(req, nullptr, 0);
|
return httpd_resp_send(req, nullptr, 0);
|
||||||
}
|
}
|
||||||
@@ -173,7 +426,7 @@ esp_err_t wifiPostHandler(httpd_req_t* req)
|
|||||||
return httpd_resp_send(req, json.c_str(), json.size());
|
return httpd_resp_send(req, json.c_str(), json.size());
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!gStore->saveWifiCredentials(parsed.value())) {
|
if (!ctx->store->saveWifiCredentials(parsed.value())) {
|
||||||
const std::string json =
|
const std::string json =
|
||||||
core::serializeWifiProvisionErrorJson("store_failed");
|
core::serializeWifiProvisionErrorJson("store_failed");
|
||||||
httpd_resp_set_status(req, "500 Internal Server Error");
|
httpd_resp_set_status(req, "500 Internal Server Error");
|
||||||
@@ -196,6 +449,8 @@ SetupWebServer::SetupWebServer()
|
|||||||
: server_(nullptr)
|
: server_(nullptr)
|
||||||
, store_(nullptr)
|
, store_(nullptr)
|
||||||
, netState_(NetState::Uninitialized)
|
, netState_(NetState::Uninitialized)
|
||||||
|
, tuner_(nullptr)
|
||||||
|
, routeContext_{nullptr, nullptr}
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -203,11 +458,14 @@ SetupWebServer::SetupWebServer(SetupWebServer&& other) noexcept
|
|||||||
: server_(other.server_)
|
: server_(other.server_)
|
||||||
, store_(other.store_)
|
, store_(other.store_)
|
||||||
, netState_(other.netState_)
|
, netState_(other.netState_)
|
||||||
|
, tuner_(other.tuner_)
|
||||||
|
, routeContext_(other.routeContext_)
|
||||||
{
|
{
|
||||||
other.server_ = nullptr;
|
other.server_ = nullptr;
|
||||||
other.store_ = nullptr;
|
other.store_ = nullptr;
|
||||||
other.netState_ = NetState::Uninitialized;
|
other.netState_ = NetState::Uninitialized;
|
||||||
gActiveServer = this;
|
other.tuner_ = nullptr;
|
||||||
|
other.routeContext_ = {nullptr, nullptr};
|
||||||
}
|
}
|
||||||
|
|
||||||
SetupWebServer& SetupWebServer::operator=(SetupWebServer&& other) noexcept
|
SetupWebServer& SetupWebServer::operator=(SetupWebServer&& other) noexcept
|
||||||
@@ -219,30 +477,30 @@ SetupWebServer& SetupWebServer::operator=(SetupWebServer&& other) noexcept
|
|||||||
server_ = other.server_;
|
server_ = other.server_;
|
||||||
store_ = other.store_;
|
store_ = other.store_;
|
||||||
netState_ = other.netState_;
|
netState_ = other.netState_;
|
||||||
|
tuner_ = other.tuner_;
|
||||||
|
routeContext_ = other.routeContext_;
|
||||||
other.server_ = nullptr;
|
other.server_ = nullptr;
|
||||||
other.store_ = nullptr;
|
other.store_ = nullptr;
|
||||||
other.netState_ = NetState::Uninitialized;
|
other.netState_ = NetState::Uninitialized;
|
||||||
gActiveServer = this;
|
other.tuner_ = nullptr;
|
||||||
|
other.routeContext_ = {nullptr, nullptr};
|
||||||
}
|
}
|
||||||
return *this;
|
return *this;
|
||||||
}
|
}
|
||||||
|
|
||||||
SetupWebServer::~SetupWebServer()
|
SetupWebServer::~SetupWebServer()
|
||||||
{
|
{
|
||||||
if (gActiveServer == this) {
|
|
||||||
gActiveServer = nullptr;
|
|
||||||
}
|
|
||||||
if (gStore == store_) {
|
|
||||||
gStore = nullptr;
|
|
||||||
}
|
|
||||||
if (server_ != nullptr) {
|
if (server_ != nullptr) {
|
||||||
httpd_stop(server_);
|
httpd_stop(server_);
|
||||||
server_ = nullptr;
|
server_ = nullptr;
|
||||||
}
|
}
|
||||||
|
routeContext_ = {nullptr, nullptr};
|
||||||
}
|
}
|
||||||
|
|
||||||
std::expected<void, NetError> SetupWebServer::start(core::ISecureStore& store,
|
std::expected<void, NetError> SetupWebServer::start(
|
||||||
NetState netState)
|
core::ISecureStore& store,
|
||||||
|
NetState netState,
|
||||||
|
tuner::TunerService& tuner)
|
||||||
{
|
{
|
||||||
if (server_ != nullptr) {
|
if (server_ != nullptr) {
|
||||||
return {};
|
return {};
|
||||||
@@ -250,8 +508,9 @@ std::expected<void, NetError> SetupWebServer::start(core::ISecureStore& store,
|
|||||||
|
|
||||||
store_ = &store;
|
store_ = &store;
|
||||||
netState_ = netState;
|
netState_ = netState;
|
||||||
gActiveServer = this;
|
tuner_ = &tuner;
|
||||||
gStore = &store;
|
routeContext_.store = &store;
|
||||||
|
routeContext_.tuner = &tuner;
|
||||||
|
|
||||||
httpd_config_t config = HTTPD_DEFAULT_CONFIG();
|
httpd_config_t config = HTTPD_DEFAULT_CONFIG();
|
||||||
config.server_port = 80;
|
config.server_port = 80;
|
||||||
@@ -259,16 +518,17 @@ std::expected<void, NetError> SetupWebServer::start(core::ISecureStore& store,
|
|||||||
|
|
||||||
if (httpd_start(&server_, &config) != ESP_OK) {
|
if (httpd_start(&server_, &config) != ESP_OK) {
|
||||||
ESP_LOGE(kTag, "httpd_start failed");
|
ESP_LOGE(kTag, "httpd_start failed");
|
||||||
gActiveServer = nullptr;
|
routeContext_ = {nullptr, nullptr};
|
||||||
gStore = nullptr;
|
|
||||||
return std::unexpected(NetError::HttpServerStartFailed);
|
return std::unexpected(NetError::HttpServerStartFailed);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void* routeCtx = &routeContext_;
|
||||||
|
|
||||||
const httpd_uri_t healthUri = {
|
const httpd_uri_t healthUri = {
|
||||||
.uri = "/api/health",
|
.uri = "/api/health",
|
||||||
.method = HTTP_GET,
|
.method = HTTP_GET,
|
||||||
.handler = healthGetHandler,
|
.handler = healthGetHandler,
|
||||||
.user_ctx = nullptr,
|
.user_ctx = routeCtx,
|
||||||
};
|
};
|
||||||
httpd_register_uri_handler(server_, &healthUri);
|
httpd_register_uri_handler(server_, &healthUri);
|
||||||
|
|
||||||
@@ -276,7 +536,7 @@ std::expected<void, NetError> SetupWebServer::start(core::ISecureStore& store,
|
|||||||
.uri = "/",
|
.uri = "/",
|
||||||
.method = HTTP_GET,
|
.method = HTTP_GET,
|
||||||
.handler = indexGetHandler,
|
.handler = indexGetHandler,
|
||||||
.user_ctx = nullptr,
|
.user_ctx = routeCtx,
|
||||||
};
|
};
|
||||||
httpd_register_uri_handler(server_, &indexUri);
|
httpd_register_uri_handler(server_, &indexUri);
|
||||||
|
|
||||||
@@ -284,10 +544,50 @@ std::expected<void, NetError> SetupWebServer::start(core::ISecureStore& store,
|
|||||||
.uri = "/api/wifi",
|
.uri = "/api/wifi",
|
||||||
.method = HTTP_POST,
|
.method = HTTP_POST,
|
||||||
.handler = wifiPostHandler,
|
.handler = wifiPostHandler,
|
||||||
.user_ctx = nullptr,
|
.user_ctx = routeCtx,
|
||||||
};
|
};
|
||||||
httpd_register_uri_handler(server_, &wifiUri);
|
httpd_register_uri_handler(server_, &wifiUri);
|
||||||
|
|
||||||
|
const httpd_uri_t tunerStatusUri = {
|
||||||
|
.uri = "/api/tuner/status",
|
||||||
|
.method = HTTP_GET,
|
||||||
|
.handler = tunerStatusGetHandler,
|
||||||
|
.user_ctx = routeCtx,
|
||||||
|
};
|
||||||
|
httpd_register_uri_handler(server_, &tunerStatusUri);
|
||||||
|
|
||||||
|
const httpd_uri_t tunerServicesUri = {
|
||||||
|
.uri = "/api/tuner/services",
|
||||||
|
.method = HTTP_GET,
|
||||||
|
.handler = tunerServicesGetHandler,
|
||||||
|
.user_ctx = routeCtx,
|
||||||
|
};
|
||||||
|
httpd_register_uri_handler(server_, &tunerServicesUri);
|
||||||
|
|
||||||
|
const httpd_uri_t tunerTuneUri = {
|
||||||
|
.uri = "/api/tuner/tune",
|
||||||
|
.method = HTTP_POST,
|
||||||
|
.handler = tunerTunePostHandler,
|
||||||
|
.user_ctx = routeCtx,
|
||||||
|
};
|
||||||
|
httpd_register_uri_handler(server_, &tunerTuneUri);
|
||||||
|
|
||||||
|
const httpd_uri_t tunerPlayUri = {
|
||||||
|
.uri = "/api/tuner/play",
|
||||||
|
.method = HTTP_POST,
|
||||||
|
.handler = tunerPlayPostHandler,
|
||||||
|
.user_ctx = routeCtx,
|
||||||
|
};
|
||||||
|
httpd_register_uri_handler(server_, &tunerPlayUri);
|
||||||
|
|
||||||
|
const httpd_uri_t tunerSeekUri = {
|
||||||
|
.uri = "/api/tuner/seek",
|
||||||
|
.method = HTTP_POST,
|
||||||
|
.handler = tunerSeekPostHandler,
|
||||||
|
.user_ctx = routeCtx,
|
||||||
|
};
|
||||||
|
httpd_register_uri_handler(server_, &tunerSeekUri);
|
||||||
|
|
||||||
ESP_LOGI(kTag, "HTTP server listening on port 80");
|
ESP_LOGI(kTag, "HTTP server listening on port 80");
|
||||||
return {};
|
return {};
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,8 @@
|
|||||||
|
idf_component_register(
|
||||||
|
SRCS
|
||||||
|
"src/TunerService.cpp"
|
||||||
|
INCLUDE_DIRS "include"
|
||||||
|
REQUIRES core
|
||||||
|
)
|
||||||
|
|
||||||
|
target_compile_features(${COMPONENT_LIB} PUBLIC cxx_std_23)
|
||||||
@@ -0,0 +1,173 @@
|
|||||||
|
/**
|
||||||
|
* @file TunerService.hpp
|
||||||
|
* @brief Application service orchestrating tuner operations for UI/API.
|
||||||
|
*
|
||||||
|
* DigiRadio firmware — https://github.com/manvalan/DigiRadio
|
||||||
|
*
|
||||||
|
* Copyright 2026 Michele Bigi
|
||||||
|
* SPDX-License-Identifier: Apache-2.0
|
||||||
|
*
|
||||||
|
* @author Michele Bigi
|
||||||
|
* @date 2026-07-06
|
||||||
|
*/
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "core/FrequencyKHz.hpp"
|
||||||
|
#include "core/ITuner.hpp"
|
||||||
|
#include "core/SeekDirection.hpp"
|
||||||
|
#include "core/TunerError.hpp"
|
||||||
|
#include "core/TunerStatus.hpp"
|
||||||
|
|
||||||
|
#include <cstdint>
|
||||||
|
#include <expected>
|
||||||
|
#include <vector>
|
||||||
|
|
||||||
|
namespace tuner {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief TunerService — intent-level tuner API for HTTP and future UI.
|
||||||
|
*
|
||||||
|
* @dname TunerService
|
||||||
|
* @return n/a (type)
|
||||||
|
* @pubstate Borrows core::ITuner for the process lifetime. Tracks last tune
|
||||||
|
* target and cached volume for status reporting. No public data
|
||||||
|
* members.
|
||||||
|
*
|
||||||
|
* Delegates hardware to core::ITuner; maps driver failures to TunerError
|
||||||
|
* without exposing SPI details to the shell.
|
||||||
|
*
|
||||||
|
* @author Michele Bigi
|
||||||
|
* @date 2026-07-06
|
||||||
|
*/
|
||||||
|
class TunerService {
|
||||||
|
public:
|
||||||
|
/**
|
||||||
|
* @brief TunerService — bind to a tuner driver for the process lifetime.
|
||||||
|
*
|
||||||
|
* @dname TunerService
|
||||||
|
* @param tuner Driver implementation (must outlive this service).
|
||||||
|
* @pubstate stores tuner reference; initialises last tune defaults.
|
||||||
|
*
|
||||||
|
* @author Michele Bigi
|
||||||
|
* @date 2026-07-06
|
||||||
|
*/
|
||||||
|
explicit TunerService(core::ITuner& tuner);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief refreshStatus — read a fresh tuner snapshot from the driver.
|
||||||
|
*
|
||||||
|
* @dname refreshStatus
|
||||||
|
* @return TunerStatus on success, or a TunerError from ITuner.
|
||||||
|
* @pubstate reads tuner_; updates cached volume_ on success.
|
||||||
|
*
|
||||||
|
* @author Michele Bigi
|
||||||
|
* @date 2026-07-06
|
||||||
|
*/
|
||||||
|
[[nodiscard]] std::expected<core::TunerStatus, core::TunerError>
|
||||||
|
refreshStatus();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief tuneDab — tune to a Band III ensemble index.
|
||||||
|
*
|
||||||
|
* @dname tuneDab
|
||||||
|
* @param freqIndex Ensemble index 0–37.
|
||||||
|
* @return Ok on success, or a TunerError from ITuner.
|
||||||
|
* @pubstate writes lastDabIndex_ on success.
|
||||||
|
*
|
||||||
|
* @author Michele Bigi
|
||||||
|
* @date 2026-07-06
|
||||||
|
*/
|
||||||
|
[[nodiscard]] std::expected<void, core::TunerError> tuneDab(
|
||||||
|
std::uint8_t freqIndex);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief tuneFm — tune to an FM centre frequency.
|
||||||
|
*
|
||||||
|
* @dname tuneFm
|
||||||
|
* @param frequency Validated FM centre frequency.
|
||||||
|
* @return Ok on success, or a TunerError from ITuner.
|
||||||
|
* @pubstate writes lastFmFrequency_ on success.
|
||||||
|
*
|
||||||
|
* @author Michele Bigi
|
||||||
|
* @date 2026-07-06
|
||||||
|
*/
|
||||||
|
[[nodiscard]] std::expected<void, core::TunerError> tuneFm(
|
||||||
|
core::FrequencyKHz frequency);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief seekFm — seek FM in the given direction.
|
||||||
|
*
|
||||||
|
* @dname seekFm
|
||||||
|
* @param direction Up or Down scan direction.
|
||||||
|
* @return New centre frequency, or a TunerError.
|
||||||
|
* @pubstate writes lastFmFrequency_ on success.
|
||||||
|
*
|
||||||
|
* @author Michele Bigi
|
||||||
|
* @date 2026-07-06
|
||||||
|
*/
|
||||||
|
[[nodiscard]] std::expected<core::FrequencyKHz, core::TunerError> seekFm(
|
||||||
|
core::SeekDirection direction);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief listDabServices — programmes available on the current ensemble.
|
||||||
|
*
|
||||||
|
* @dname listDabServices
|
||||||
|
* @return Service entries, or a TunerError from ITuner.
|
||||||
|
* @pubstate reads tuner_.
|
||||||
|
*
|
||||||
|
* @author Michele Bigi
|
||||||
|
* @date 2026-07-06
|
||||||
|
*/
|
||||||
|
[[nodiscard]] std::expected<std::vector<core::TunerServiceEntry>,
|
||||||
|
core::TunerError>
|
||||||
|
listDabServices();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief playDabService — start playback of a DAB programme.
|
||||||
|
*
|
||||||
|
* @dname playDabService
|
||||||
|
* @param serviceId Selected service identifier.
|
||||||
|
* @param componentId Audio component within the service.
|
||||||
|
* @return Ok on success, or a TunerError from ITuner.
|
||||||
|
* @pubstate delegates to tuner_.
|
||||||
|
*
|
||||||
|
* @author Michele Bigi
|
||||||
|
* @date 2026-07-06
|
||||||
|
*/
|
||||||
|
[[nodiscard]] std::expected<void, core::TunerError> playDabService(
|
||||||
|
std::uint32_t serviceId, std::uint32_t componentId);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief setVolume — set tuner output attenuation.
|
||||||
|
*
|
||||||
|
* @dname setVolume
|
||||||
|
* @param level Attenuator 0–63.
|
||||||
|
* @return Ok on success, or a TunerError from ITuner.
|
||||||
|
* @pubstate writes volume_ on success.
|
||||||
|
*
|
||||||
|
* @author Michele Bigi
|
||||||
|
* @date 2026-07-06
|
||||||
|
*/
|
||||||
|
[[nodiscard]] std::expected<void, core::TunerError> setVolume(
|
||||||
|
std::uint8_t level);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief tuner — borrow the underlying driver for diagnostics.
|
||||||
|
*
|
||||||
|
* @dname tuner
|
||||||
|
* @return Reference to the injected ITuner implementation.
|
||||||
|
* @pubstate reads tuner_.
|
||||||
|
*
|
||||||
|
* @author Michele Bigi
|
||||||
|
* @date 2026-07-06
|
||||||
|
*/
|
||||||
|
[[nodiscard]] core::ITuner& tuner() noexcept;
|
||||||
|
|
||||||
|
private:
|
||||||
|
core::ITuner& tuner_;
|
||||||
|
std::uint8_t lastDabIndex_;
|
||||||
|
core::FrequencyKHz lastFmFrequency_;
|
||||||
|
std::uint8_t volume_;
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace tuner
|
||||||
@@ -0,0 +1,101 @@
|
|||||||
|
/**
|
||||||
|
* @file TunerService.cpp
|
||||||
|
* @brief TunerService implementation.
|
||||||
|
*
|
||||||
|
* DigiRadio firmware — https://github.com/manvalan/DigiRadio
|
||||||
|
*
|
||||||
|
* Copyright 2026 Michele Bigi
|
||||||
|
* SPDX-License-Identifier: Apache-2.0
|
||||||
|
*
|
||||||
|
* @author Michele Bigi
|
||||||
|
* @date 2026-07-06
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include "tuner/TunerService.hpp"
|
||||||
|
|
||||||
|
namespace tuner {
|
||||||
|
|
||||||
|
namespace {
|
||||||
|
|
||||||
|
[[nodiscard]] core::FrequencyKHz defaultFmFrequency()
|
||||||
|
{
|
||||||
|
return *core::FrequencyKHz::tryFromKhz(101500U);
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace
|
||||||
|
|
||||||
|
TunerService::TunerService(core::ITuner& tuner)
|
||||||
|
: tuner_(tuner)
|
||||||
|
, lastDabIndex_(0U)
|
||||||
|
, lastFmFrequency_(defaultFmFrequency())
|
||||||
|
, volume_(40U)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
std::expected<core::TunerStatus, core::TunerError> TunerService::refreshStatus()
|
||||||
|
{
|
||||||
|
auto status = tuner_.readStatus();
|
||||||
|
if (status) {
|
||||||
|
volume_ = status->volume;
|
||||||
|
}
|
||||||
|
return status;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::expected<void, core::TunerError> TunerService::tuneDab(
|
||||||
|
std::uint8_t freqIndex)
|
||||||
|
{
|
||||||
|
if (auto result = tuner_.tuneDab(freqIndex); !result) {
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
lastDabIndex_ = freqIndex;
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
|
||||||
|
std::expected<void, core::TunerError> TunerService::tuneFm(
|
||||||
|
core::FrequencyKHz frequency)
|
||||||
|
{
|
||||||
|
if (auto result = tuner_.tuneFm(frequency); !result) {
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
lastFmFrequency_ = frequency;
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
|
||||||
|
std::expected<core::FrequencyKHz, core::TunerError> TunerService::seekFm(
|
||||||
|
core::SeekDirection direction)
|
||||||
|
{
|
||||||
|
auto result = tuner_.seekFm(direction);
|
||||||
|
if (result) {
|
||||||
|
lastFmFrequency_ = *result;
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::expected<std::vector<core::TunerServiceEntry>, core::TunerError>
|
||||||
|
TunerService::listDabServices()
|
||||||
|
{
|
||||||
|
return tuner_.listDabServices();
|
||||||
|
}
|
||||||
|
|
||||||
|
std::expected<void, core::TunerError> TunerService::playDabService(
|
||||||
|
std::uint32_t serviceId,
|
||||||
|
std::uint32_t componentId)
|
||||||
|
{
|
||||||
|
return tuner_.playDabService(serviceId, componentId);
|
||||||
|
}
|
||||||
|
|
||||||
|
std::expected<void, core::TunerError> TunerService::setVolume(std::uint8_t level)
|
||||||
|
{
|
||||||
|
if (auto result = tuner_.setVolume(level); !result) {
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
volume_ = level & 0x3FU;
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
|
||||||
|
core::ITuner& TunerService::tuner() noexcept
|
||||||
|
{
|
||||||
|
return tuner_;
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace tuner
|
||||||
@@ -46,11 +46,12 @@ Repository: https://github.com/manvalan/DigiRadio
|
|||||||
|
|
||||||
1. **Walking skeleton** — done (Slice 1).
|
1. **Walking skeleton** — done (Slice 1).
|
||||||
2. **Secure store + Wi-Fi provisioning** — done (Slice 2).
|
2. **Secure store + Wi-Fi provisioning** — done (Slice 2).
|
||||||
3. Station/frequency list model + persistence + UI.
|
3. **Companion-chip boot** — done (Slice 3): Si4684 DAB + ADAU1701 RAM load.
|
||||||
4. Si4684 driver: power-up, load image, tune, read RSQ.
|
4. Station/frequency list model + persistence + UI.
|
||||||
5. ADAU1701 driver: RAM boot, then safeload EQ + input mixer.
|
5. Si4684 tuning: RSQ, station list, DAB properties.
|
||||||
6. FSC-BT1035 driver: AT init (incl. `AT+AUXCFG=1`), audio out.
|
6. ADAU1701 runtime: safeload EQ + input mixer.
|
||||||
7. Integration: TunerService + AudioService end to end.
|
7. FSC-BT1035 driver: AT init (incl. `AT+AUXCFG=1`), audio out.
|
||||||
|
8. Integration: TunerService + AudioService end to end.
|
||||||
|
|
||||||
## Slice 1 — Walking skeleton (complete)
|
## Slice 1 — Walking skeleton (complete)
|
||||||
|
|
||||||
@@ -69,7 +70,7 @@ Behaviour:
|
|||||||
- Bring up SoftAP with a known SSID (e.g. `DigiRadio-setup`).
|
- Bring up SoftAP with a known SSID (e.g. `DigiRadio-setup`).
|
||||||
- Start an HTTP server serving one minimal gzipped page from flash.
|
- Start an HTTP server serving one minimal gzipped page from flash.
|
||||||
- Expose `GET /api/health` returning a typed DTO serialised by the pure
|
- Expose `GET /api/health` returning a typed DTO serialised by the pure
|
||||||
core, e.g. `{"status":"ok","fw":"0.2.0"}`.
|
core, e.g. `{"status":"ok","fw":"0.3.0"}`.
|
||||||
|
|
||||||
Documentation (required):
|
Documentation (required):
|
||||||
- Doxygen doc blocks on every class/method; `doxygen Doxyfile` green.
|
- Doxygen doc blocks on every class/method; `doxygen Doxyfile` green.
|
||||||
@@ -96,3 +97,25 @@ Acceptance criteria:
|
|||||||
|
|
||||||
Out of scope: station list, user credentials, NVS encryption enablement
|
Out of scope: station list, user credentials, NVS encryption enablement
|
||||||
(production), chip drivers.
|
(production), chip drivers.
|
||||||
|
|
||||||
|
## Slice 3 — Companion-chip boot (complete)
|
||||||
|
|
||||||
|
Goal: load Si4684 DAB firmware and ADAU1701 SigmaStudio program from
|
||||||
|
`Firmware/` on every boot, before network bring-up.
|
||||||
|
|
||||||
|
Build:
|
||||||
|
- `Firmware/Si4684-Firmware/` — `rom_patch_016.bin`, `dab_firmware.bin`,
|
||||||
|
`fm_firmware.bin` (FM via `tools/fetch_si4684_firmware.py --si46xx-dir`).
|
||||||
|
- `Firmware/ADAU1701-Firmware/` — SigmaStudio export (`DigiRadio_IC_1.h`, …).
|
||||||
|
- `core::IFirmwareBlobReader` + `EmbeddedBlobReader` for chunked HOST_LOAD.
|
||||||
|
- `si4684::Si4684Driver`, `adau1701::Adau1701Driver`, `HardwareBootstrap`.
|
||||||
|
|
||||||
|
Behaviour:
|
||||||
|
- `app_main` calls `HardwareBootstrap::boot()` first (Si4684, then ADAU1701).
|
||||||
|
- On failure, firmware logs and halts before Wi-Fi.
|
||||||
|
|
||||||
|
Acceptance criteria:
|
||||||
|
- [x] AN649 boot sequence with streaming blobs (no full image on heap).
|
||||||
|
- [x] ADAU1701 reset + I2C + `default_download_IC_1()` replay.
|
||||||
|
- [x] Host test for `EmbeddedBlobReader`; manual sync green.
|
||||||
|
- [ ] Device flash verified (requires ESP-IDF toolchain on build host).
|
||||||
|
|||||||
@@ -1,5 +1,7 @@
|
|||||||
idf_component_register(
|
idf_component_register(
|
||||||
SRCS "main.cpp"
|
SRCS
|
||||||
|
"main.cpp"
|
||||||
|
"hardware_bootstrap.cpp"
|
||||||
INCLUDE_DIRS "."
|
INCLUDE_DIRS "."
|
||||||
REQUIRES core net secure_store
|
REQUIRES core net secure_store adau1701 si4684 tuner
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -0,0 +1,87 @@
|
|||||||
|
/**
|
||||||
|
* @file hardware_bootstrap.cpp
|
||||||
|
* @brief HardwareBootstrap implementation.
|
||||||
|
*
|
||||||
|
* DigiRadio firmware — https://github.com/manvalan/DigiRadio
|
||||||
|
*
|
||||||
|
* Copyright 2026 Michele Bigi
|
||||||
|
* SPDX-License-Identifier: Apache-2.0
|
||||||
|
*
|
||||||
|
* @author Michele Bigi
|
||||||
|
* @date 2026-07-06
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include "hardware_bootstrap.hpp"
|
||||||
|
|
||||||
|
#include "adau1701/Adau1701Driver.hpp"
|
||||||
|
#include "board_pins.hpp"
|
||||||
|
#include "si4684/Si4684Band.hpp"
|
||||||
|
#include "si4684/Si4684Driver.hpp"
|
||||||
|
#include "si4684/Si4684EmbeddedImages.hpp"
|
||||||
|
#include "si4684/Si4684Tuner.hpp"
|
||||||
|
|
||||||
|
#include "driver/spi_master.h"
|
||||||
|
#include "esp_log.h"
|
||||||
|
|
||||||
|
namespace hardware {
|
||||||
|
|
||||||
|
namespace {
|
||||||
|
constexpr char kTag[] = "hw_boot";
|
||||||
|
|
||||||
|
si4684::Si4684EmbeddedImages gImages;
|
||||||
|
si4684::Si4684Driver gSi4684(
|
||||||
|
si4684::Si4684Pins{
|
||||||
|
.spiHost = SPI2_HOST,
|
||||||
|
.csGpio = board::pins::Si4684Cs,
|
||||||
|
.misoGpio = board::pins::Si4684Miso,
|
||||||
|
.mosiGpio = board::pins::Si4684Mosi,
|
||||||
|
.sclkGpio = board::pins::Si4684Sclk,
|
||||||
|
.rstbGpio = board::pins::Si4684Rstb,
|
||||||
|
.intbGpio = board::pins::Si4684Intb,
|
||||||
|
},
|
||||||
|
gImages.romPatch(),
|
||||||
|
gImages.dabFirmware(),
|
||||||
|
gImages.fmFirmware());
|
||||||
|
si4684::Si4684Tuner gSi4684Tuner(gSi4684);
|
||||||
|
|
||||||
|
adau1701::Adau1701Driver gAdau1701(
|
||||||
|
adau1701::Adau1701Pins{
|
||||||
|
.i2cSda = board::pins::Adau1701Sda,
|
||||||
|
.i2cScl = board::pins::Adau1701Scl,
|
||||||
|
.resetGpio = board::pins::Adau1701Reset,
|
||||||
|
.i2cAddr7 = board::pins::Adau1701Addr,
|
||||||
|
});
|
||||||
|
|
||||||
|
bool gReady = false;
|
||||||
|
} // namespace
|
||||||
|
|
||||||
|
std::expected<void, HardwareBootError> HardwareBootstrap::boot()
|
||||||
|
{
|
||||||
|
if (gReady) {
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
|
||||||
|
if (auto tunerResult = gSi4684.boot(si4684::Si4684Band::Dab); !tunerResult) {
|
||||||
|
ESP_LOGE(kTag, "Si4684 boot failed");
|
||||||
|
return std::unexpected(HardwareBootError::Si4684BootFailed);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!gAdau1701.isBooted()) {
|
||||||
|
auto dspResult = gAdau1701.boot();
|
||||||
|
if (!dspResult) {
|
||||||
|
ESP_LOGE(kTag, "ADAU1701 boot failed");
|
||||||
|
return std::unexpected(HardwareBootError::Adau1701BootFailed);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
gReady = true;
|
||||||
|
ESP_LOGI(kTag, "companion chips ready");
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
|
||||||
|
si4684::Si4684Tuner& HardwareBootstrap::si4684Tuner()
|
||||||
|
{
|
||||||
|
return gSi4684Tuner;
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace hardware
|
||||||
@@ -0,0 +1,80 @@
|
|||||||
|
/**
|
||||||
|
* @file hardware_bootstrap.hpp
|
||||||
|
* @brief Boot Si4684 and ADAU1701 companion chips at application start.
|
||||||
|
*
|
||||||
|
* DigiRadio firmware — https://github.com/manvalan/DigiRadio
|
||||||
|
*
|
||||||
|
* Copyright 2026 Michele Bigi
|
||||||
|
* SPDX-License-Identifier: Apache-2.0
|
||||||
|
*
|
||||||
|
* @author Michele Bigi
|
||||||
|
* @date 2026-07-06
|
||||||
|
*/
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <expected>
|
||||||
|
|
||||||
|
namespace si4684 {
|
||||||
|
class Si4684Tuner;
|
||||||
|
} // namespace si4684
|
||||||
|
|
||||||
|
namespace hardware {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief HardwareBootError — companion-chip boot failure causes.
|
||||||
|
*
|
||||||
|
* @dname HardwareBootError
|
||||||
|
* @return n/a (type)
|
||||||
|
* @pubstate n/a
|
||||||
|
*
|
||||||
|
* @author Michele Bigi
|
||||||
|
* @date 2026-07-06
|
||||||
|
*/
|
||||||
|
enum class HardwareBootError {
|
||||||
|
Si4684BootFailed,
|
||||||
|
Adau1701BootFailed,
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief HardwareBootstrap — orchestrates Si4684 then ADAU1701 bring-up.
|
||||||
|
*
|
||||||
|
* @dname HardwareBootstrap
|
||||||
|
* @return n/a (type)
|
||||||
|
* @pubstate Owns static driver instances for the process lifetime. Must be
|
||||||
|
* called once from app_main before network start.
|
||||||
|
*
|
||||||
|
* @author Michele Bigi
|
||||||
|
* @date 2026-07-06
|
||||||
|
*/
|
||||||
|
class HardwareBootstrap {
|
||||||
|
public:
|
||||||
|
/**
|
||||||
|
* @brief boot — load Si4684 DAB image then replay ADAU1701 program.
|
||||||
|
*
|
||||||
|
* @dname boot
|
||||||
|
* @return Ok on success, or HardwareBootError.
|
||||||
|
* @pubstate constructs static Si4684 and ADAU1701 drivers on first call.
|
||||||
|
*
|
||||||
|
* Fail-closed: callers must not start Wi-Fi when this returns an error.
|
||||||
|
*
|
||||||
|
* @author Michele Bigi
|
||||||
|
* @date 2026-07-06
|
||||||
|
*/
|
||||||
|
[[nodiscard]] static std::expected<void, HardwareBootError> boot();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief si4684Tuner — borrow the Si4684 ITuner adapter after boot.
|
||||||
|
*
|
||||||
|
* @dname si4684Tuner
|
||||||
|
* @return Reference to the static Si4684Tuner instance.
|
||||||
|
* @pubstate reads static storage initialised by boot().
|
||||||
|
*
|
||||||
|
* Valid only after a successful boot() call in the same process.
|
||||||
|
*
|
||||||
|
* @author Michele Bigi
|
||||||
|
* @date 2026-07-06
|
||||||
|
*/
|
||||||
|
[[nodiscard]] static si4684::Si4684Tuner& si4684Tuner();
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace hardware
|
||||||
+15
-22
@@ -7,17 +7,14 @@
|
|||||||
* Copyright 2026 Michele Bigi
|
* Copyright 2026 Michele Bigi
|
||||||
* SPDX-License-Identifier: Apache-2.0
|
* SPDX-License-Identifier: Apache-2.0
|
||||||
*
|
*
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
||||||
* you may not use this file except in compliance with the License.
|
|
||||||
* You may obtain a copy of the License at
|
|
||||||
* http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
*
|
|
||||||
* @author Michele Bigi
|
* @author Michele Bigi
|
||||||
* @date 2026-07-06
|
* @date 2026-07-06
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
#include "hardware_bootstrap.hpp"
|
||||||
#include "net/NetBootstrap.hpp"
|
#include "net/NetBootstrap.hpp"
|
||||||
#include "secure_store/NvsSecureStore.hpp"
|
#include "secure_store/NvsSecureStore.hpp"
|
||||||
|
#include "tuner/TunerService.hpp"
|
||||||
|
|
||||||
#include "esp_log.h"
|
#include "esp_log.h"
|
||||||
#include "freertos/FreeRTOS.h"
|
#include "freertos/FreeRTOS.h"
|
||||||
@@ -28,16 +25,6 @@ namespace {
|
|||||||
constexpr char kTag[] = "digiradio";
|
constexpr char kTag[] = "digiradio";
|
||||||
constexpr TickType_t kHeartbeatPeriod = pdMS_TO_TICKS(5000);
|
constexpr TickType_t kHeartbeatPeriod = pdMS_TO_TICKS(5000);
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief heartbeatTask — periodic alive log for bring-up verification.
|
|
||||||
*
|
|
||||||
* @dname heartbeatTask
|
|
||||||
* @param arg Unused task parameter.
|
|
||||||
* @pubstate none
|
|
||||||
*
|
|
||||||
* @author Michele Bigi
|
|
||||||
* @date 2026-07-06
|
|
||||||
*/
|
|
||||||
void heartbeatTask(void* arg)
|
void heartbeatTask(void* arg)
|
||||||
{
|
{
|
||||||
(void)arg;
|
(void)arg;
|
||||||
@@ -50,24 +37,30 @@ void heartbeatTask(void* arg)
|
|||||||
} // namespace
|
} // namespace
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @brief app_main — ESP-IDF application entry point.
|
* @brief app_main — ESP-IDF entry point for DigiRadio firmware.
|
||||||
*
|
*
|
||||||
* @dname app_main
|
* @dname app_main
|
||||||
* @pubstate starts NvsSecureStore, NetBootstrap, and the heartbeat task.
|
* @pubstate boots companion chips, network stack, and heartbeat task.
|
||||||
*
|
|
||||||
* Slice 2: joins a stored Wi-Fi network when credentials exist, otherwise
|
|
||||||
* opens the DigiRadio-setup SoftAP for provisioning via the web UI.
|
|
||||||
*
|
*
|
||||||
* @author Michele Bigi
|
* @author Michele Bigi
|
||||||
* @date 2026-07-06
|
* @date 2026-07-06
|
||||||
*/
|
*/
|
||||||
extern "C" void app_main()
|
extern "C" void app_main()
|
||||||
{
|
{
|
||||||
ESP_LOGI(kTag, "DigiRadio firmware boot — Slice 2");
|
ESP_LOGI(kTag, "DigiRadio firmware boot — Slice 4");
|
||||||
|
|
||||||
|
auto hwResult = hardware::HardwareBootstrap::boot();
|
||||||
|
if (!hwResult) {
|
||||||
|
ESP_LOGE(kTag, "companion chip boot failed — halting");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
static tuner::TunerService tunerService(
|
||||||
|
hardware::HardwareBootstrap::si4684Tuner());
|
||||||
|
|
||||||
static secure_store::NvsSecureStore store;
|
static secure_store::NvsSecureStore store;
|
||||||
|
|
||||||
auto netResult = net::NetBootstrap::start(store);
|
auto netResult = net::NetBootstrap::start(store, tunerService);
|
||||||
if (!netResult) {
|
if (!netResult) {
|
||||||
ESP_LOGE(kTag, "network bootstrap failed");
|
ESP_LOGE(kTag, "network bootstrap failed");
|
||||||
return;
|
return;
|
||||||
|
|||||||
Executable
+88
@@ -0,0 +1,88 @@
|
|||||||
|
#!/usr/bin/env python3
|
||||||
|
"""extract_si4684_blob.py — convert Si4684 C arrays or copies to .bin files.
|
||||||
|
|
||||||
|
DigiRadio firmware — https://github.com/manvalan/DigiRadio
|
||||||
|
|
||||||
|
Copyright 2026 Michele Bigi
|
||||||
|
SPDX-License-Identifier: Apache-2.0
|
||||||
|
|
||||||
|
Usage:
|
||||||
|
# From a SigmaStudio-style C header (PE5PVB firmware.h, Si468xROM.h):
|
||||||
|
tools/extract_si4684_blob.py --header src/firmware.h --array firmware \\
|
||||||
|
--out Firmware/Si4684-Firmware/dab_firmware.bin
|
||||||
|
|
||||||
|
# Copy an existing Skyworks .bin from an eval-board folder:
|
||||||
|
tools/extract_si4684_blob.py --copy ~/si46xx_firmware/fm_radio_5_1_0.bin \\
|
||||||
|
--out Firmware/Si4684-Firmware/fm_firmware.bin
|
||||||
|
"""
|
||||||
|
|
||||||
|
from __future__ import annotations
|
||||||
|
|
||||||
|
import argparse
|
||||||
|
import re
|
||||||
|
import shutil
|
||||||
|
import sys
|
||||||
|
from pathlib import Path
|
||||||
|
|
||||||
|
|
||||||
|
def extract_c_array(header: Path, array_name: str) -> bytes:
|
||||||
|
text = header.read_text(encoding="utf-8", errors="replace")
|
||||||
|
pattern = rf"{re.escape(array_name)}\s*\[\s*\]\s*=\s*\{{(.*?)\}};"
|
||||||
|
match = re.search(pattern, text, re.S)
|
||||||
|
if not match:
|
||||||
|
raise SystemExit(f"array {array_name!r} not found in {header}")
|
||||||
|
values = re.findall(r"0x[0-9a-fA-F]+", match.group(1))
|
||||||
|
if not values:
|
||||||
|
raise SystemExit(f"array {array_name!r} is empty in {header}")
|
||||||
|
return bytes(int(v, 16) for v in values)
|
||||||
|
|
||||||
|
|
||||||
|
def extract_flash_region(dump: Path, offset: int, size: int) -> bytes:
|
||||||
|
data = dump.read_bytes()
|
||||||
|
end = offset + size
|
||||||
|
if end > len(data):
|
||||||
|
raise SystemExit(
|
||||||
|
f"flash dump too small: need {end} bytes, have {len(data)}"
|
||||||
|
)
|
||||||
|
return data[offset:end]
|
||||||
|
|
||||||
|
|
||||||
|
def main() -> int:
|
||||||
|
ap = argparse.ArgumentParser(description=__doc__)
|
||||||
|
ap.add_argument("--out", type=Path, required=True, help="output .bin path")
|
||||||
|
src = ap.add_mutually_exclusive_group(required=True)
|
||||||
|
src.add_argument("--header", type=Path, help="C header with byte array")
|
||||||
|
src.add_argument("--copy", type=Path, help="existing firmware .bin to copy")
|
||||||
|
src.add_argument("--flash-dump", type=Path, help="full SPI flash dump")
|
||||||
|
ap.add_argument("--array", help="array name when using --header")
|
||||||
|
ap.add_argument(
|
||||||
|
"--offset",
|
||||||
|
type=lambda s: int(s, 0),
|
||||||
|
default=0x106000,
|
||||||
|
help="byte offset for --flash-dump (default: FM region from dirb.tech)",
|
||||||
|
)
|
||||||
|
ap.add_argument(
|
||||||
|
"--size",
|
||||||
|
type=lambda s: int(s, 0),
|
||||||
|
default=0x81721,
|
||||||
|
help="byte length for --flash-dump (default: FM V5.1.0 size)",
|
||||||
|
)
|
||||||
|
args = ap.parse_args()
|
||||||
|
|
||||||
|
if args.header is not None:
|
||||||
|
if not args.array:
|
||||||
|
ap.error("--array is required with --header")
|
||||||
|
payload = extract_c_array(args.header, args.array)
|
||||||
|
elif args.copy is not None:
|
||||||
|
payload = args.copy.read_bytes()
|
||||||
|
else:
|
||||||
|
payload = extract_flash_region(args.flash_dump, args.offset, args.size)
|
||||||
|
|
||||||
|
args.out.parent.mkdir(parents=True, exist_ok=True)
|
||||||
|
args.out.write_bytes(payload)
|
||||||
|
print(f"wrote {args.out} ({len(payload)} bytes)")
|
||||||
|
return 0
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
raise SystemExit(main())
|
||||||
@@ -0,0 +1,322 @@
|
|||||||
|
#!/usr/bin/env python3
|
||||||
|
"""extract_ugreen_radio_cli.py — pull Si468x blobs from uGreen radio_cli ELF.
|
||||||
|
|
||||||
|
DigiRadio firmware — https://github.com/manvalan/DigiRadio
|
||||||
|
|
||||||
|
Copyright 2026 Michele Bigi
|
||||||
|
SPDX-License-Identifier: Apache-2.0
|
||||||
|
|
||||||
|
uGreen embeds Skyworks firmware inside ``radio_cli`` (Files_v16.zip). This module
|
||||||
|
locates ``fw_*`` rodata symbols and writes raw ``.bin`` payloads for DigiRadio
|
||||||
|
HOST_LOAD boot (AN649 / PE5PVB flow).
|
||||||
|
|
||||||
|
Obtain Files_v16.zip from https://ugreen.eu/downloads/ (DABBoard customer pack).
|
||||||
|
Do not redistribute extracted blobs in public repositories.
|
||||||
|
"""
|
||||||
|
|
||||||
|
from __future__ import annotations
|
||||||
|
|
||||||
|
import argparse
|
||||||
|
import struct
|
||||||
|
import subprocess
|
||||||
|
import sys
|
||||||
|
import zipfile
|
||||||
|
from dataclasses import dataclass
|
||||||
|
from pathlib import Path
|
||||||
|
|
||||||
|
# Exact HOST_LOAD sizes observed in radio_cli power_up_and_load (ARM64 builds).
|
||||||
|
UGREEN_KNOWN_SIZES: dict[str, int] = {
|
||||||
|
"fw_rom00_patch016bin": 5796,
|
||||||
|
"fw_dab_radio_5_0_5bin": 521_448,
|
||||||
|
"fw_dab_radio_6_0_6bin": 496_944,
|
||||||
|
"fw_fmhd_radio_5_0_4bin": 530_184,
|
||||||
|
"fw_fmhd_radio_5_1_3bin": 531_300,
|
||||||
|
}
|
||||||
|
|
||||||
|
FM_SYMBOL_PRIORITY = (
|
||||||
|
"fw_fmhd_radio_5_1_3bin",
|
||||||
|
"fw_fmhd_radio_5_1_0bin",
|
||||||
|
"fw_fm_radio_5_1_0bin",
|
||||||
|
"fw_fmhd_radio_5_0_4bin",
|
||||||
|
"fw_fm_radio_5_0_9bin",
|
||||||
|
)
|
||||||
|
|
||||||
|
DAB_SYMBOL_PRIORITY = (
|
||||||
|
"fw_dab_radio_6_0_6bin",
|
||||||
|
"fw_dab_radio_5_0_8bin",
|
||||||
|
"fw_dab_radio_5_0_5bin",
|
||||||
|
"fw_dab_radio_4_0_5bif",
|
||||||
|
)
|
||||||
|
|
||||||
|
PATCH_SYMBOL = "fw_rom00_patch016bin"
|
||||||
|
|
||||||
|
RADIO_CLI_GLOB = ("radio_cli_v*", "radio_cli")
|
||||||
|
|
||||||
|
|
||||||
|
@dataclass(frozen=True)
|
||||||
|
class FirmwareBlob:
|
||||||
|
symbol: str
|
||||||
|
offset: int
|
||||||
|
size: int
|
||||||
|
data: bytes
|
||||||
|
|
||||||
|
|
||||||
|
def resolve_radio_cli(path: Path) -> Path:
|
||||||
|
"""Return path to radio_cli ELF inside a file, zip, or directory."""
|
||||||
|
path = path.expanduser().resolve()
|
||||||
|
if path.is_file() and zipfile.is_zipfile(path):
|
||||||
|
with zipfile.ZipFile(path) as zf:
|
||||||
|
names = [
|
||||||
|
n
|
||||||
|
for n in zf.namelist()
|
||||||
|
if "radio_cli" in Path(n).name and not n.endswith("/")
|
||||||
|
]
|
||||||
|
if not names:
|
||||||
|
raise SystemExit(f"No radio_cli binary found inside {path}")
|
||||||
|
# Prefer 64-bit builds (same blobs, easier to test on desktop nm).
|
||||||
|
names.sort(key=lambda n: ("64-bit" not in n, n))
|
||||||
|
chosen = names[0]
|
||||||
|
tmp_dir = Path(path.parent, f".ugreen-extract-{path.stem}")
|
||||||
|
tmp_dir.mkdir(exist_ok=True)
|
||||||
|
out = tmp_dir / Path(chosen).name
|
||||||
|
if not out.exists() or out.stat().st_mtime < path.stat().st_mtime:
|
||||||
|
out.write_bytes(zf.read(chosen))
|
||||||
|
return out
|
||||||
|
|
||||||
|
if path.is_dir():
|
||||||
|
for pattern in ("**/bin/64-bit/radio_cli*", "**/bin/32-bit/radio_cli*"):
|
||||||
|
matches = sorted(path.glob(pattern))
|
||||||
|
if matches:
|
||||||
|
return matches[0]
|
||||||
|
for pattern in RADIO_CLI_GLOB:
|
||||||
|
matches = sorted(path.rglob(pattern))
|
||||||
|
if matches:
|
||||||
|
return matches[0]
|
||||||
|
raise SystemExit(f"No radio_cli binary under {path}")
|
||||||
|
|
||||||
|
if path.is_file():
|
||||||
|
return path
|
||||||
|
|
||||||
|
raise SystemExit(f"Path not found: {path}")
|
||||||
|
|
||||||
|
|
||||||
|
def _run_nm(elf: Path) -> str:
|
||||||
|
for cmd in (["llvm-nm", str(elf)], ["nm", str(elf)]):
|
||||||
|
try:
|
||||||
|
proc = subprocess.run(
|
||||||
|
cmd, capture_output=True, text=True, check=False
|
||||||
|
)
|
||||||
|
except FileNotFoundError:
|
||||||
|
continue
|
||||||
|
if proc.returncode == 0 and proc.stdout.strip():
|
||||||
|
return proc.stdout
|
||||||
|
raise SystemExit(
|
||||||
|
f"Could not read symbols from {elf} (install nm or llvm-nm)"
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def _parse_nm_output(text: str) -> dict[str, int]:
|
||||||
|
symbols: dict[str, int] = {}
|
||||||
|
for line in text.splitlines():
|
||||||
|
parts = line.split()
|
||||||
|
if len(parts) < 3:
|
||||||
|
continue
|
||||||
|
addr_s, sym_type, name = parts[0], parts[1], parts[2]
|
||||||
|
if sym_type not in {"R", "D", "r"}:
|
||||||
|
continue
|
||||||
|
if not name.startswith("fw_"):
|
||||||
|
continue
|
||||||
|
try:
|
||||||
|
symbols[name] = int(addr_s, 16)
|
||||||
|
except ValueError:
|
||||||
|
continue
|
||||||
|
return symbols
|
||||||
|
|
||||||
|
|
||||||
|
def _elf_vma_to_file_offset(elf: Path, vma: int) -> int:
|
||||||
|
data = elf.read_bytes()
|
||||||
|
if data[:4] != b"\x7fELF":
|
||||||
|
raise SystemExit(f"Not an ELF file: {elf}")
|
||||||
|
|
||||||
|
ei_class = data[4]
|
||||||
|
if ei_class == 1:
|
||||||
|
e_shoff = struct.unpack_from("<I", data, 0x20)[0]
|
||||||
|
e_shentsize = struct.unpack_from("<H", data, 0x2E)[0]
|
||||||
|
e_shnum = struct.unpack_from("<H", data, 0x30)[0]
|
||||||
|
e_shstrndx = struct.unpack_from("<H", data, 0x32)[0]
|
||||||
|
sh_fmt = "<IIIIIIII"
|
||||||
|
elif ei_class == 2:
|
||||||
|
e_shoff = struct.unpack_from("<Q", data, 0x28)[0]
|
||||||
|
e_shentsize = struct.unpack_from("<H", data, 0x3A)[0]
|
||||||
|
e_shnum = struct.unpack_from("<H", data, 0x3C)[0]
|
||||||
|
e_shstrndx = struct.unpack_from("<H", data, 0x3E)[0]
|
||||||
|
sh_fmt = "<IIQQQQIIQQ"
|
||||||
|
else:
|
||||||
|
raise SystemExit(f"Unsupported ELF class in {elf}")
|
||||||
|
|
||||||
|
if e_shnum == 0:
|
||||||
|
return vma
|
||||||
|
|
||||||
|
def read_section(index: int) -> tuple[int, int, int]:
|
||||||
|
off = e_shoff + index * e_shentsize
|
||||||
|
fields = struct.unpack_from(sh_fmt, data, off)
|
||||||
|
if ei_class == 1:
|
||||||
|
sh_name, sh_type, sh_flags, sh_addr, sh_offset, sh_size = fields[:6]
|
||||||
|
else:
|
||||||
|
sh_name, sh_type, sh_flags, sh_addr, sh_offset, sh_size = fields[:6]
|
||||||
|
return sh_addr, sh_offset, sh_size
|
||||||
|
|
||||||
|
shstr_off = read_section(e_shstrndx)[1]
|
||||||
|
|
||||||
|
for index in range(e_shnum):
|
||||||
|
sh_addr, sh_offset, sh_size = read_section(index)
|
||||||
|
if sh_size == 0:
|
||||||
|
continue
|
||||||
|
if sh_addr <= vma < sh_addr + sh_size:
|
||||||
|
return sh_offset + (vma - sh_addr)
|
||||||
|
|
||||||
|
return vma
|
||||||
|
|
||||||
|
|
||||||
|
def _pick_symbol(symbols: dict[str, int], priority: tuple[str, ...]) -> str | None:
|
||||||
|
for name in priority:
|
||||||
|
if name in symbols:
|
||||||
|
return name
|
||||||
|
return None
|
||||||
|
|
||||||
|
|
||||||
|
def _blob_size(
|
||||||
|
symbol: str,
|
||||||
|
vma: int,
|
||||||
|
ordered: list[tuple[str, int]],
|
||||||
|
file_size: int,
|
||||||
|
file_offset: int,
|
||||||
|
) -> int:
|
||||||
|
if symbol in UGREEN_KNOWN_SIZES:
|
||||||
|
return UGREEN_KNOWN_SIZES[symbol]
|
||||||
|
|
||||||
|
for name, addr in ordered:
|
||||||
|
if addr > vma:
|
||||||
|
return addr - vma
|
||||||
|
|
||||||
|
return file_size - file_offset
|
||||||
|
|
||||||
|
|
||||||
|
def extract_blob(elf: Path, symbol: str, symbols: dict[str, int]) -> FirmwareBlob:
|
||||||
|
if symbol not in symbols:
|
||||||
|
raise SystemExit(f"Symbol {symbol!r} not found in {elf}")
|
||||||
|
|
||||||
|
vma = symbols[symbol]
|
||||||
|
file_offset = _elf_vma_to_file_offset(elf, vma)
|
||||||
|
ordered = sorted(symbols.items(), key=lambda item: item[1])
|
||||||
|
size = _blob_size(
|
||||||
|
symbol, vma, ordered, elf.stat().st_size, file_offset
|
||||||
|
)
|
||||||
|
|
||||||
|
data = elf.read_bytes()
|
||||||
|
end = file_offset + size
|
||||||
|
if end > len(data):
|
||||||
|
raise SystemExit(
|
||||||
|
f"{symbol}: need {size} bytes at file offset 0x{file_offset:x}, "
|
||||||
|
f"ELF size is {len(data)}"
|
||||||
|
)
|
||||||
|
|
||||||
|
payload = data[file_offset:end]
|
||||||
|
if len(payload) != size:
|
||||||
|
raise SystemExit(f"{symbol}: short read ({len(payload)} != {size})")
|
||||||
|
|
||||||
|
return FirmwareBlob(symbol, file_offset, size, payload)
|
||||||
|
|
||||||
|
|
||||||
|
def extract_images(
|
||||||
|
source: Path,
|
||||||
|
*,
|
||||||
|
want_fm: bool = True,
|
||||||
|
want_dab: bool = False,
|
||||||
|
want_patch: bool = False,
|
||||||
|
) -> dict[str, FirmwareBlob]:
|
||||||
|
elf = resolve_radio_cli(source)
|
||||||
|
symbols = _parse_nm_output(_run_nm(elf))
|
||||||
|
if not symbols:
|
||||||
|
raise SystemExit(f"No fw_* symbols in {elf}")
|
||||||
|
|
||||||
|
out: dict[str, FirmwareBlob] = {}
|
||||||
|
|
||||||
|
if want_patch:
|
||||||
|
out["patch"] = extract_blob(elf, PATCH_SYMBOL, symbols)
|
||||||
|
|
||||||
|
if want_dab:
|
||||||
|
dab_sym = _pick_symbol(symbols, DAB_SYMBOL_PRIORITY)
|
||||||
|
if dab_sym is None:
|
||||||
|
raise SystemExit(f"No DAB fw_* symbol in {elf}")
|
||||||
|
out["dab"] = extract_blob(elf, dab_sym, symbols)
|
||||||
|
|
||||||
|
if want_fm:
|
||||||
|
fm_sym = _pick_symbol(symbols, FM_SYMBOL_PRIORITY)
|
||||||
|
if fm_sym is None:
|
||||||
|
raise SystemExit(f"No FM fw_* symbol in {elf}")
|
||||||
|
out["fm"] = extract_blob(elf, fm_sym, symbols)
|
||||||
|
|
||||||
|
return out
|
||||||
|
|
||||||
|
|
||||||
|
def write_blobs(blobs: dict[str, FirmwareBlob], out_dir: Path) -> None:
|
||||||
|
mapping = {
|
||||||
|
"patch": "rom_patch_016.bin",
|
||||||
|
"dab": "dab_firmware.bin",
|
||||||
|
"fm": "fm_firmware.bin",
|
||||||
|
}
|
||||||
|
out_dir.mkdir(parents=True, exist_ok=True)
|
||||||
|
for key, blob in blobs.items():
|
||||||
|
dest = out_dir / mapping[key]
|
||||||
|
dest.write_bytes(blob.data)
|
||||||
|
print(
|
||||||
|
f"wrote {dest} ({len(blob.data)} bytes) "
|
||||||
|
f"from {blob.symbol} @ 0x{blob.offset:x}"
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def main() -> int:
|
||||||
|
ap = argparse.ArgumentParser(description=__doc__)
|
||||||
|
ap.add_argument(
|
||||||
|
"source",
|
||||||
|
type=Path,
|
||||||
|
help="radio_cli ELF, Files_v16.zip, or unpacked Files_v16 directory",
|
||||||
|
)
|
||||||
|
ap.add_argument(
|
||||||
|
"--out-dir",
|
||||||
|
type=Path,
|
||||||
|
default=Path(__file__).resolve().parents[1] / "Firmware" / "Si4684-Firmware",
|
||||||
|
help="output directory for .bin files",
|
||||||
|
)
|
||||||
|
ap.add_argument("--fm", action="store_true", help="extract FM image")
|
||||||
|
ap.add_argument("--dab", action="store_true", help="extract DAB image")
|
||||||
|
ap.add_argument("--patch", action="store_true", help="extract ROM patch")
|
||||||
|
ap.add_argument(
|
||||||
|
"--all",
|
||||||
|
action="store_true",
|
||||||
|
help="extract patch, DAB, and FM",
|
||||||
|
)
|
||||||
|
args = ap.parse_args()
|
||||||
|
|
||||||
|
if args.all:
|
||||||
|
want_fm = want_dab = want_patch = True
|
||||||
|
else:
|
||||||
|
want_fm = args.fm
|
||||||
|
want_dab = args.dab
|
||||||
|
want_patch = args.patch
|
||||||
|
if not (want_fm or want_dab or want_patch):
|
||||||
|
want_fm = True
|
||||||
|
|
||||||
|
blobs = extract_images(
|
||||||
|
args.source,
|
||||||
|
want_fm=want_fm,
|
||||||
|
want_dab=want_dab,
|
||||||
|
want_patch=want_patch,
|
||||||
|
)
|
||||||
|
write_blobs(blobs, args.out_dir)
|
||||||
|
return 0
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
raise SystemExit(main())
|
||||||
Executable
+232
@@ -0,0 +1,232 @@
|
|||||||
|
#!/usr/bin/env python3
|
||||||
|
"""fetch_si4684_firmware.py — populate Firmware/Si4684-Firmware/ blobs.
|
||||||
|
|
||||||
|
DigiRadio firmware — https://github.com/manvalan/DigiRadio
|
||||||
|
|
||||||
|
Copyright 2026 Michele Bigi
|
||||||
|
SPDX-License-Identifier: Apache-2.0
|
||||||
|
|
||||||
|
DAB images are extracted from PE5PVB/SI4684-DAB-Receiver (same as Slice 3).
|
||||||
|
FM images are proprietary Skyworks blobs — supply via --si46xx-dir (eval CD /
|
||||||
|
dabpi si46xx_firmware folder), --flash-dump (TechniSat-style SPI readout), or
|
||||||
|
--from-ugreen-radio-cli (uGreen DABBoard Files_v16.zip / radio_cli ELF).
|
||||||
|
|
||||||
|
AN649 Si4684-A10: FM application image fm_radio_5_1_0.bin (or community
|
||||||
|
fmhd_radio_5_0_4.bin on Si4688-class boards — verify on hardware).
|
||||||
|
"""
|
||||||
|
|
||||||
|
from __future__ import annotations
|
||||||
|
|
||||||
|
import argparse
|
||||||
|
import subprocess
|
||||||
|
import sys
|
||||||
|
import tempfile
|
||||||
|
from pathlib import Path
|
||||||
|
|
||||||
|
_TOOLS = Path(__file__).resolve().parent
|
||||||
|
if str(_TOOLS) not in sys.path:
|
||||||
|
sys.path.insert(0, str(_TOOLS))
|
||||||
|
|
||||||
|
from extract_ugreen_radio_cli import extract_images, write_blobs # noqa: E402
|
||||||
|
|
||||||
|
ROOT = _TOOLS.parent
|
||||||
|
FW_DIR = ROOT / "Firmware" / "Si4684-Firmware"
|
||||||
|
EXTRACT = _TOOLS / "extract_si4684_blob.py"
|
||||||
|
PE5PVB = "https://github.com/PE5PVB/SI4684-DAB-Receiver.git"
|
||||||
|
|
||||||
|
FM_CANDIDATE_NAMES = (
|
||||||
|
"fm_radio_5_1_0.bin",
|
||||||
|
"fm_radio_5_0_9.bin",
|
||||||
|
"fmhd_radio_5_1_0.bin",
|
||||||
|
"fmhd_radio_5_0_4.bin",
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def run_extract(args: list[str]) -> None:
|
||||||
|
cmd = [sys.executable, str(EXTRACT), *args]
|
||||||
|
subprocess.run(cmd, check=True)
|
||||||
|
|
||||||
|
|
||||||
|
def clone_pe5pvb(tmp: Path) -> Path:
|
||||||
|
dest = tmp / "SI4684-DAB-Receiver"
|
||||||
|
if not dest.exists():
|
||||||
|
subprocess.run(
|
||||||
|
["git", "clone", "--depth", "1", PE5PVB, str(dest)],
|
||||||
|
check=True,
|
||||||
|
)
|
||||||
|
return dest
|
||||||
|
|
||||||
|
|
||||||
|
def refresh_dab(tmp: Path) -> None:
|
||||||
|
repo = clone_pe5pvb(tmp)
|
||||||
|
run_extract(
|
||||||
|
[
|
||||||
|
"--header",
|
||||||
|
str(repo / "src" / "Si468xROM.h"),
|
||||||
|
"--array",
|
||||||
|
"rom_patch_016",
|
||||||
|
"--out",
|
||||||
|
str(FW_DIR / "rom_patch_016.bin"),
|
||||||
|
]
|
||||||
|
)
|
||||||
|
run_extract(
|
||||||
|
[
|
||||||
|
"--header",
|
||||||
|
str(repo / "src" / "firmware.h"),
|
||||||
|
"--array",
|
||||||
|
"firmware",
|
||||||
|
"--out",
|
||||||
|
str(FW_DIR / "dab_firmware.bin"),
|
||||||
|
]
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def find_fm_source(si46xx_dir: Path) -> Path | None:
|
||||||
|
for name in FM_CANDIDATE_NAMES:
|
||||||
|
candidate = si46xx_dir / name
|
||||||
|
if candidate.is_file():
|
||||||
|
return candidate
|
||||||
|
return None
|
||||||
|
|
||||||
|
|
||||||
|
def import_fm(source: Path) -> None:
|
||||||
|
run_extract(["--copy", str(source), "--out", str(FW_DIR / "fm_firmware.bin")])
|
||||||
|
|
||||||
|
|
||||||
|
def import_fm_from_flash(dump: Path, offset: int, size: int) -> None:
|
||||||
|
run_extract(
|
||||||
|
[
|
||||||
|
"--flash-dump",
|
||||||
|
str(dump),
|
||||||
|
"--offset",
|
||||||
|
hex(offset),
|
||||||
|
"--size",
|
||||||
|
hex(size),
|
||||||
|
"--out",
|
||||||
|
str(FW_DIR / "fm_firmware.bin"),
|
||||||
|
]
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def import_from_ugreen(
|
||||||
|
source: Path,
|
||||||
|
*,
|
||||||
|
want_fm: bool,
|
||||||
|
want_dab: bool,
|
||||||
|
want_patch: bool,
|
||||||
|
) -> None:
|
||||||
|
blobs = extract_images(
|
||||||
|
source,
|
||||||
|
want_fm=want_fm,
|
||||||
|
want_dab=want_dab,
|
||||||
|
want_patch=want_patch,
|
||||||
|
)
|
||||||
|
write_blobs(blobs, FW_DIR)
|
||||||
|
|
||||||
|
|
||||||
|
def main() -> int:
|
||||||
|
ap = argparse.ArgumentParser(description=__doc__)
|
||||||
|
ap.add_argument(
|
||||||
|
"--si46xx-dir",
|
||||||
|
type=Path,
|
||||||
|
help="folder with Skyworks .bin files (eval CD / dabpi si46xx_firmware)",
|
||||||
|
)
|
||||||
|
ap.add_argument(
|
||||||
|
"--flash-dump",
|
||||||
|
type=Path,
|
||||||
|
help="full external SPI dump (TechniSat-style layout)",
|
||||||
|
)
|
||||||
|
ap.add_argument(
|
||||||
|
"--fm-offset",
|
||||||
|
type=lambda s: int(s, 0),
|
||||||
|
default=0x106000,
|
||||||
|
help="FM image offset inside --flash-dump",
|
||||||
|
)
|
||||||
|
ap.add_argument(
|
||||||
|
"--fm-size",
|
||||||
|
type=lambda s: int(s, 0),
|
||||||
|
default=0x81721,
|
||||||
|
help="FM image size inside --flash-dump",
|
||||||
|
)
|
||||||
|
ap.add_argument(
|
||||||
|
"--dab-only",
|
||||||
|
action="store_true",
|
||||||
|
help="refresh DAB + patch only (skip FM)",
|
||||||
|
)
|
||||||
|
ap.add_argument(
|
||||||
|
"--from-ugreen-radio-cli",
|
||||||
|
type=Path,
|
||||||
|
metavar="PATH",
|
||||||
|
help="uGreen Files_v16.zip, radio_cli ELF, or unpacked Files_v16 folder",
|
||||||
|
)
|
||||||
|
ap.add_argument(
|
||||||
|
"--ugreen-all",
|
||||||
|
action="store_true",
|
||||||
|
help="with --from-ugreen-radio-cli: extract patch, DAB, and FM from radio_cli",
|
||||||
|
)
|
||||||
|
args = ap.parse_args()
|
||||||
|
|
||||||
|
FW_DIR.mkdir(parents=True, exist_ok=True)
|
||||||
|
|
||||||
|
ugreen = args.from_ugreen_radio_cli is not None
|
||||||
|
if ugreen and args.dab_only:
|
||||||
|
ap.error("--dab-only cannot be combined with --from-ugreen-radio-cli")
|
||||||
|
|
||||||
|
fm_sources = int(args.si46xx_dir is not None) + int(
|
||||||
|
args.flash_dump is not None
|
||||||
|
) + int(ugreen)
|
||||||
|
if fm_sources > 1:
|
||||||
|
ap.error(
|
||||||
|
"choose one FM source: --si46xx-dir, --flash-dump, or "
|
||||||
|
"--from-ugreen-radio-cli"
|
||||||
|
)
|
||||||
|
|
||||||
|
with tempfile.TemporaryDirectory(prefix="digiradio-si4684-") as tmp_name:
|
||||||
|
tmp = Path(tmp_name)
|
||||||
|
|
||||||
|
if args.dab_only:
|
||||||
|
refresh_dab(tmp)
|
||||||
|
print("DAB + patch refreshed; FM unchanged.")
|
||||||
|
return 0
|
||||||
|
|
||||||
|
ugreen_full = ugreen and args.ugreen_all
|
||||||
|
if not ugreen_full:
|
||||||
|
refresh_dab(tmp)
|
||||||
|
|
||||||
|
if ugreen:
|
||||||
|
import_from_ugreen(
|
||||||
|
args.from_ugreen_radio_cli,
|
||||||
|
want_fm=True,
|
||||||
|
want_dab=ugreen_full,
|
||||||
|
want_patch=ugreen_full,
|
||||||
|
)
|
||||||
|
elif args.si46xx_dir is not None:
|
||||||
|
fm = find_fm_source(args.si46xx_dir)
|
||||||
|
if fm is None:
|
||||||
|
print(
|
||||||
|
f"No FM image found in {args.si46xx_dir}; expected one of:",
|
||||||
|
", ".join(FM_CANDIDATE_NAMES),
|
||||||
|
file=sys.stderr,
|
||||||
|
)
|
||||||
|
return 1
|
||||||
|
import_fm(fm)
|
||||||
|
elif args.flash_dump is not None:
|
||||||
|
import_fm_from_flash(args.flash_dump, args.fm_offset, args.fm_size)
|
||||||
|
else:
|
||||||
|
print(
|
||||||
|
"FM image not updated: pass --si46xx-dir, --flash-dump, or "
|
||||||
|
"--from-ugreen-radio-cli.\n"
|
||||||
|
"Skyworks fm_radio_5_1_0.bin is on the Si4684 eval CD "
|
||||||
|
"(see AN649 table 1).",
|
||||||
|
file=sys.stderr,
|
||||||
|
)
|
||||||
|
return 1
|
||||||
|
|
||||||
|
fm_path = FW_DIR / "fm_firmware.bin"
|
||||||
|
if fm_path.is_file():
|
||||||
|
print(f"FM image ready: {fm_path} ({fm_path.stat().st_size} bytes)")
|
||||||
|
return 0
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
raise SystemExit(main())
|
||||||
Reference in New Issue
Block a user