From acd3a5f0cefed9b6f6edb85584d54f104d8de961 Mon Sep 17 00:00:00 2001 From: Aidan MacDonald Date: Wed, 18 Mar 2026 12:42:18 +0000 Subject: [PATCH] echoplayer: implement ADC to read battery voltage Change-Id: I8043e7d2f02c10cb8c9d9ec59b7d216945431481 --- firmware/SOURCES | 2 +- firmware/reggen/stm32h743.regs | 248 ++++++++++++++++++ firmware/target/arm/stm32/adc-stm32h7.c | 25 -- .../arm/stm32/echoplayer/adc-echoplayer.c | 140 ++++++++++ .../target/arm/stm32/echoplayer/adc-target.h | 4 + .../arm/stm32/echoplayer/clock-echoplayer.c | 1 + .../target/arm/stm32/echoplayer/gpio-target.h | 3 + .../arm/stm32/echoplayer/power-echoplayer.c | 3 +- .../arm/stm32/echoplayer/system-echoplayer.c | 3 + 9 files changed, 402 insertions(+), 27 deletions(-) delete mode 100644 firmware/target/arm/stm32/adc-stm32h7.c create mode 100644 firmware/target/arm/stm32/echoplayer/adc-echoplayer.c diff --git a/firmware/SOURCES b/firmware/SOURCES index ca90c2f3d7..14365f965e 100644 --- a/firmware/SOURCES +++ b/firmware/SOURCES @@ -1926,7 +1926,6 @@ target/arm/rk27xx/ihifi2/audio-ihifi800.c #if CONFIG_CPU == STM32H743 target/arm/stm32/crt0-stm32h7.S target/arm/stm32/vectors-stm32h7.S -target/arm/stm32/adc-stm32h7.c #ifndef BOOTLOADER target/arm/stm32/debug-stm32h7.c #endif @@ -1942,6 +1941,7 @@ target/arm/stm32/usb-stm32h7.c #endif #if defined(ECHO_R1) +target/arm/stm32/echoplayer/adc-echoplayer.c target/arm/stm32/echoplayer/audiohw-echoplayer.c target/arm/stm32/echoplayer/backlight-echoplayer.c target/arm/stm32/echoplayer/button-echoplayer.c diff --git a/firmware/reggen/stm32h743.regs b/firmware/reggen/stm32h743.regs index 7dc6153c2c..03292c347c 100644 --- a/firmware/reggen/stm32h743.regs +++ b/firmware/reggen/stm32h743.regs @@ -1509,3 +1509,251 @@ I2C1 @ 0x40005400 : I2C I2C2 @ 0x40005800 : I2C I2C3 @ 0x40005C00 : I2C I2C4 @ 0x58001C00 : I2C + +block ADC { + ISR @ 0x00 : reg { + 12 LDORDY + 10 JQOVF + 09 AWD3 + 08 AWD2 + 07 AWD1 + 06 JEOS + 05 JEOC + 04 OVR + 03 EOS + 02 EOC + 01 EOSMP + 00 ADRDY + } + + IER @ 0x04 : reg { + 10 JQOVFIE + 09 AWD3IE + 08 AWD2IE + 07 AWD1IE + 06 JEOSIE + 05 JEOCIE + 04 OVRIE + 03 EOSIE + 02 EOCIE + 01 EOSMPIE + 00 ADRDYIE + } + + CR @ 0x08 : reg { + -- 31 ADCAL + -- 30 ADCALDIF + -- 29 DEEPPWD + -- 28 ADVREGEN + -- 27 LINCALRDYW6 + -- 26 LINCALRDYW5 + -- 25 LINCALRDYW4 + -- 24 LINCALRDYW3 + -- 23 LINCALRDYW2 + -- 22 LINCALRDYW1 + -- 16 ADCALLIN + -- 09 BOOST1 // present on revision V only + -- 08 BOOST0 + -- 05 JADSTP + -- 04 ADSTP + -- 03 JADSTART + -- 02 ADSTART + -- 01 ADDIS + -- 00 ADEN + } + + CFGR @ 0x0c : reg { + -- 31 JQDIS + 30 26 AWD1CH + -- 25 JAUTO + -- 24 JAWD1EN + -- 23 AWD1EN + -- 22 AWD1SGL + -- 21 JQM + -- 20 JDISCEN + 19 17 DISCNUM + -- 16 DISCEN + -- 14 AUTDLY + -- 13 CONT + -- 12 OVRMOD + 11 10 EXTEN + 09 05 EXTSEL + 04 02 RES + 01 00 DMNGT + } + + CFGR2 @ 0x10 : reg { + 31 28 LSHIFT + 25 16 OSVR + -- 14 RSHIFT4 + -- 13 RSHIFT3 + -- 12 RSHIFT2 + -- 11 RSHIFT1 + -- 10 ROVSM + -- 09 TROVS + 08 05 OVSS + -- 01 JOVSE + -- 00 ROVSE + } + + enum SMPTIME { + 0 = 1_5CYCLE + 1 = 2_5CYCLE + 2 = 8_5CYCLE + 3 = 16_5CYCLE + 4 = 32_5CYCLE + 5 = 64_5CYCLE + 6 = 387_5CYCLE + 7 = 810_5CYCLE + } + + SMPR1 @ 0x14 : reg { + 29 27 SMP9 : SMPTIME + 26 24 SMP8 : SMPTIME + 23 21 SMP7 : SMPTIME + 20 18 SMP6 : SMPTIME + 17 15 SMP5 : SMPTIME + 14 12 SMP4 : SMPTIME + 11 09 SMP3 : SMPTIME + 08 06 SMP2 : SMPTIME + 05 03 SMP1 : SMPTIME + 02 00 SMP0 : SMPTIME + } + + SMPR2 @ 0x18 : reg { + 29 27 SMP19 : SMPTIME + 26 24 SMP18 : SMPTIME + 23 21 SMP17 : SMPTIME + 20 18 SMP16 : SMPTIME + 17 15 SMP15 : SMPTIME + 14 12 SMP14 : SMPTIME + 11 09 SMP13 : SMPTIME + 08 06 SMP12 : SMPTIME + 05 03 SMP11 : SMPTIME + 02 00 SMP10 : SMPTIME + } + + PCSEL @ 0x1c : reg + LTR1 @ 0x20 : reg + HTR1 @ 0x24 : reg + + SQR1 @ 0x30 : reg { + 28 24 SQ4 + 22 18 SQ3 + 16 12 SQ2 + 10 06 SQ1 + 03 00 L + } + + SQR2 @ 0x34 : reg { + 28 24 SQ9 + 22 18 SQ8 + 16 12 SQ7 + 10 06 SQ6 + 04 00 SQ5 + } + + SQR3 @ 0x38 : reg { + 28 24 SQ14 + 22 18 SQ13 + 16 12 SQ12 + 10 06 SQ11 + 04 00 SQ10 + } + + SQR4 @ 0x38 : reg { + 10 06 SQ16 + 04 00 SQ15 + } + + DR @ 0x40 : reg + + JSQR @ 0x4c : reg { + 31 27 JSQ4 + 25 21 JSQ3 + 19 15 JSQ2 + 13 09 JSQ1 + 08 07 JEXTEN + 06 02 JEXTSEL + 01 00 JL + } + + OFR @ 0x60 [4; 0x04] : reg { + -- 31 SSATE + 30 26 OFFSET_CH + 25 00 OFFSET + } + + JDR @ 0x80 [4; 0x04] : reg + + AWD2CR @ 0xa0 : reg { + 19 00 AWD2CH + } + + AWD3CR @ 0xa4 : reg { + 19 00 AWD3CH + } + + LTR2 @ 0xb0 : reg + HTR2 @ 0xb4 : reg + LTR3 @ 0xb8 : reg + HTR3 @ 0xbc : reg + + DIFSEL @ 0xc0 : reg + + CALFACT @ 0xc4 : reg { + 26 16 D + 10 00 S + } + + CALFACT2 @ 0xc8 : reg { + 29 00 LINCALFACT + } + + CSR @ 0x300 : reg { + 26 JQOVF_SLV + 25 AWD3_SLV + 24 AWD2_SLV + 23 AWD1_SLV + 22 JEOS_SLV + 21 JEOC_SLV + 20 OVR_SLV + 19 EOS_SLV + 18 EOC_SLV + 17 EOSMP_SLV + 16 ADRDY_SLV + 10 JQOVF_MST + 09 AWD3_MST + 08 AWD2_MST + 07 AWD1_MST + 06 JEOS_MST + 05 JEOC_MST + 04 OVR_MST + 03 EOS_MST + 02 EOC_MST + 01 EOSMP_MST + 00 ADRDY_MST + } + + CCR @ 0x308 : reg { + -- 24 VBATEN + -- 23 TSEN + -- 22 VREFEN + 21 18 PRESC + 17 16 CKMOD + 15 14 DAMDF + 10 08 DELAY + 04 00 DUAL + } + + CDR @ 0x30c : reg { + 31 16 RDATA_SLV + 15 00 RDATA_MST + } + + CDR2 @ 0x310 : reg +} + +ADC1 @ 0x40022000 : ADC +ADC2 @ 0x40022100 : ADC +ADC3 @ 0x58026000 : ADC diff --git a/firmware/target/arm/stm32/adc-stm32h7.c b/firmware/target/arm/stm32/adc-stm32h7.c deleted file mode 100644 index 8d53ba8e1f..0000000000 --- a/firmware/target/arm/stm32/adc-stm32h7.c +++ /dev/null @@ -1,25 +0,0 @@ -/*************************************************************************** - * __________ __ ___. - * Open \______ \ ____ ____ | | _\_ |__ _______ ___ - * Source | _// _ \_/ ___\| |/ /| __ \ / _ \ \/ / - * Jukebox | | ( <_> ) \___| < | \_\ ( <_> > < < - * Firmware |____|_ /\____/ \___ >__|_ \|___ /\____/__/\_ \ - * \/ \/ \/ \/ \/ - * $Id$ - * - * Copyright (C) 2025 by Aidan MacDonald - * - * This program is free software; you can redistribute it and/or - * modify it under the terms of the GNU General Public License - * as published by the Free Software Foundation; either version 2 - * of the License, or (at your option) any later version. - * - * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY - * KIND, either express or implied. - * - ****************************************************************************/ -#include "adc.h" - -void adc_init(void) -{ -} diff --git a/firmware/target/arm/stm32/echoplayer/adc-echoplayer.c b/firmware/target/arm/stm32/echoplayer/adc-echoplayer.c new file mode 100644 index 0000000000..245f0375db --- /dev/null +++ b/firmware/target/arm/stm32/echoplayer/adc-echoplayer.c @@ -0,0 +1,140 @@ +/*************************************************************************** + * __________ __ ___. + * Open \______ \ ____ ____ | | _\_ |__ _______ ___ + * Source | _// _ \_/ ___\| |/ /| __ \ / _ \ \/ / + * Jukebox | | ( <_> ) \___| < | \_\ ( <_> > < < + * Firmware |____|_ /\____/ \___ >__|_ \|___ /\____/__/\_ \ + * \/ \/ \/ \/ \/ + * $Id$ + * + * Copyright (C) 2026 by Aidan MacDonald + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY + * KIND, either express or implied. + * + ****************************************************************************/ +#include "adc.h" +#include "mutex.h" +#include "kernel.h" +#include "fixedpoint.h" +#include "regs/stm32h743/adc.h" +#include "regs/stm32h743/rcc.h" + +#define VREF_MV 3300 + +static volatile void *const adc3 = (void *)ITA_ADC3; +static struct mutex adc_mutex; + +static void set_smptime(volatile void *adcbase, int channel, uint32_t smpval) +{ + uint32_t reg_off = ITO_ADC_SMPR1; + if (channel > 10) + { + reg_off = ITO_ADC_SMPR2; + channel -= 10; + } + + volatile uint32_t *reg = adcbase + reg_off; + uint32_t reg_val = *reg; + + reg_val &= ~(0x7 << (channel * 3)); + reg_val |= smpval << (channel * 3); + + *reg = reg_val; +} + +void adc_init(void) +{ + mutex_init(&adc_mutex); + reg_writef(RCC_AHB4ENR, ADC3EN(1)); + + /* Power up ADC */ + reg_writelf(adc3, ADC_CR, DEEPPWD(0)); + reg_writelf(adc3, ADC_CR, ADVREGEN(1)); + while (!reg_readlf(adc3, ADC_ISR, LDORDY)); + + /* Run calibration */ + reg_writelf(adc3, ADC_CR, ADCALDIF(0), ADCALLIN(1), ADCAL(1)); + while (reg_readlf(adc3, ADC_CR, ADCAL)); + + /* Enable the ADC */ + reg_assignlf(adc3, ADC_ISR, ADRDY(1)); + reg_writelf(adc3, ADC_CR, ADEN(1)); + while (!reg_readlf(adc3, ADC_ISR, ADRDY)); + + /* Set sampling times for each channel (387.5 cycles @ 6 MHz = 64.5us) */ + set_smptime(adc3, ADC_CHANNEL_VBUS, BV_ADC_SMPR1_SMP0_387_5CYCLE); + set_smptime(adc3, ADC_CHANNEL_BATTERY, BV_ADC_SMPR1_SMP0_387_5CYCLE); +} + +/* + * Converts raw ADC reading to millivolts, assuming the + * ADC samples the output of a voltage divider as in the + * following circuit: + * + * [VIN]---[R1] + * | + * +----[ADC] + * | + * [R2] + * | + * [GND] + * + * The value returned is the voltage at the VIN node. + */ +static uint32_t scale_adc_val(uint32_t raw_val, + uint32_t vref_mV, + uint32_t r1_ohms, + uint32_t r2_ohms) +{ + /* + * Finds the best fracbits value which doesn't overflow. + * GCC will optimize this loop to a constant. + */ + uint32_t fracbits = 16; + uint32_t conv_fac; + for (; fracbits > 0; --fracbits) + { + conv_fac = fp_div(r1_ohms + r2_ohms, r2_ohms, fracbits); + if (conv_fac <= UINT16_MAX) + break; + } + + uint32_t conv_val = fp_mul(raw_val, conv_fac, fracbits); + return fp_mul(vref_mV, conv_val, 16); +} + +unsigned short adc_read(int channel) +{ + mutex_lock(&adc_mutex); + + /* Configure ADC to read the channel */ + reg_writelf(adc3, ADC_SQR1, L(1 - 1), SQ1(channel)); + reg_varl(adc3, ADC_PCSEL) = (1 << channel); + + /* Start conversion & wait until complete */ + reg_writelf(adc3, ADC_CR, ADSTART(1)); + while (reg_readlf(adc3, ADC_CR, ADSTART)); + + uint32_t raw_val = reg_readl(adc3, ADC_DR); + + mutex_unlock(&adc_mutex); + + /* Convert to millivolts */ + switch (channel) + { + case ADC_CHANNEL_VBUS: + return scale_adc_val(raw_val, VREF_MV, 174, 100); + + case ADC_CHANNEL_BATTERY: + return scale_adc_val(raw_val, VREF_MV, 174, 390); + + default: + return 0; + } +} diff --git a/firmware/target/arm/stm32/echoplayer/adc-target.h b/firmware/target/arm/stm32/echoplayer/adc-target.h index 6c0eafc463..5edc098b37 100644 --- a/firmware/target/arm/stm32/echoplayer/adc-target.h +++ b/firmware/target/arm/stm32/echoplayer/adc-target.h @@ -21,4 +21,8 @@ #ifndef __ECHOPLAYER_ADC_TARGET_H__ #define __ECHOPLAYER_ADC_TARGET_H__ +/* ADC3 channels */ +#define ADC_CHANNEL_VBUS 11 +#define ADC_CHANNEL_BATTERY 16 /* real battery voltage */ + #endif /* __ECHOPLAYER_ADC_TARGET_H__ */ diff --git a/firmware/target/arm/stm32/echoplayer/clock-echoplayer.c b/firmware/target/arm/stm32/echoplayer/clock-echoplayer.c index 1e2be5cd5c..d9bd0d31c5 100644 --- a/firmware/target/arm/stm32/echoplayer/clock-echoplayer.c +++ b/firmware/target/arm/stm32/echoplayer/clock-echoplayer.c @@ -165,6 +165,7 @@ INIT_ATTR static void init_periph_clock(void) reg_writef(RCC_D1CCIPR, SDMMCSEL_V(PLL1Q)); reg_writef(RCC_D2CCIP1R, SAI1SEL_V(PLL2P), SPI45SEL_V(HSE)); reg_writef(RCC_D2CCIP2R, I2C123SEL_V(HSI)); + reg_writef(RCC_D3CCIPR, ADCSEL_V(PLL3R)); /* Enable AXI SRAM in sleep mode to allow DMA'ing out of it */ reg_writef(RCC_AHB3LPENR, AXISRAMEN(1)); diff --git a/firmware/target/arm/stm32/echoplayer/gpio-target.h b/firmware/target/arm/stm32/echoplayer/gpio-target.h index 2e92f249c8..36c5df281b 100644 --- a/firmware/target/arm/stm32/echoplayer/gpio-target.h +++ b/firmware/target/arm/stm32/echoplayer/gpio-target.h @@ -55,4 +55,7 @@ #define GPIO_USB_VBUS GPIO_PA(9) +#define GPIO_ADC_VBAT GPIO_PH(5) +#define GPIO_ADC_VBUS GPIO_PC(1) + #endif /* __ECHOPLAYER_GPIO_TARGET_H__ */ diff --git a/firmware/target/arm/stm32/echoplayer/power-echoplayer.c b/firmware/target/arm/stm32/echoplayer/power-echoplayer.c index 63bf173d33..bbe3c627e5 100644 --- a/firmware/target/arm/stm32/echoplayer/power-echoplayer.c +++ b/firmware/target/arm/stm32/echoplayer/power-echoplayer.c @@ -20,6 +20,7 @@ ****************************************************************************/ #include "power.h" #include "mutex.h" +#include "adc.h" #include "gpio-stm32h7.h" #include "system-echoplayer.h" #include "regs/cortex-m/cm_scb.h" @@ -130,5 +131,5 @@ bool charging_state(void) int _battery_voltage(void) { - return 4000; + return adc_read(ADC_CHANNEL_BATTERY); } diff --git a/firmware/target/arm/stm32/echoplayer/system-echoplayer.c b/firmware/target/arm/stm32/echoplayer/system-echoplayer.c index 04e2a563ba..29d017eec9 100644 --- a/firmware/target/arm/stm32/echoplayer/system-echoplayer.c +++ b/firmware/target/arm/stm32/echoplayer/system-echoplayer.c @@ -49,6 +49,7 @@ #define F_LCD_AF9 GPIOF_FUNCTION(9, GPIO_TYPE_PUSH_PULL, GPIO_SPEED_MEDIUM, GPIO_PULL_DISABLED) #define F_LPTIM4_OUT GPIOF_FUNCTION(3, GPIO_TYPE_PUSH_PULL, GPIO_SPEED_LOW, GPIO_PULL_DISABLED) #define F_LPTIM1_OUT GPIOF_FUNCTION(1, GPIO_TYPE_PUSH_PULL, GPIO_SPEED_VERYHIGH, GPIO_PULL_DISABLED) +#define F_ANALOG GPIOF_ANALOG() #if STM32H743_USBOTG_INSTANCE == STM32H743_USBOTG_INSTANCE_USB1 # define F_OTG_FS GPIOF_ANALOG() @@ -90,6 +91,8 @@ static const struct gpio_setting gpios[] = { STM_DEFGPIO(GPIO_LCD_RESET, F_OUT_LS(0)), /* active low */ STM_DEFGPIO(GPIO_BACKLIGHT, F_LPTIM1_OUT), STM_DEFGPIO(GPIO_USB_VBUS, F_INPUT), /* active high */ + STM_DEFGPIO(GPIO_ADC_VBAT, F_ANALOG), + STM_DEFGPIO(GPIO_ADC_VBUS, F_ANALOG), }; /* TODO - replace hex constants - there are probably mistakes here */