echoplayer: implement ADC to read battery voltage

Change-Id: I8043e7d2f02c10cb8c9d9ec59b7d216945431481
This commit is contained in:
Aidan MacDonald 2026-03-18 12:42:18 +00:00
parent 684b3d8c49
commit acd3a5f0ce
9 changed files with 402 additions and 27 deletions

View file

@ -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

View file

@ -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

View file

@ -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)
{
}

View file

@ -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;
}
}

View file

@ -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__ */

View file

@ -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));

View file

@ -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__ */

View file

@ -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);
}

View file

@ -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 */