esp32c3/esp32s3: refactor ADC implementation to reduce code duplication.

This refactoring reduces code duplication from the esp32c3/esp32s3 ADC
implementation, by reusing the register/efuse calibration code since the
same basic procedures are used by both processors.

Signed-off-by: deadprogram <ron@hybridgroup.com>
This commit is contained in:
deadprogram
2026-03-01 23:21:34 +01:00
parent 86cc3b6c12
commit f902437848
3 changed files with 312 additions and 498 deletions
+43 -252
View File
@@ -5,10 +5,11 @@ package machine
import (
"device/esp"
"errors"
"runtime/volatile"
"unsafe"
)
// newRegI2C returns the regI2C configured for ESP32-C3: hostID=0, drefInit=1.
func newRegI2C() regI2C { return regI2C{hostID: 0, drefInit: 1} }
const (
// ADC attenuation values for ESP32-C3 APB_SARADC.
// 0 dB : ~0 .. 1.1 V
@@ -34,8 +35,7 @@ func InitADC() {
esp.APB_SARADC.SetCLKM_CONF_CLKM_DIV_A(0)
esp.APB_SARADC.SetCLKM_CONF_CLK_EN(1)
var c adcSelfCalibration
c.calibrate()
adcSelfCalibrate()
}
// ESP32-C3: ADC1 = GPIO0GPIO4 (ch 04), ADC2 = GPIO5 (ch 0). ADC2 shares with WiFi;
@@ -88,43 +88,43 @@ func (a ADC) Get() uint16 {
// adcSelfCalibration
const (
adcCalTimesC3 = 15
adcCalOffsetRangeC3 = uint32(4096)
adcCalRtcMagicC3 = uint32(0xADC1C401)
adcCalInitMinC3 = uint32(1000)
adcCalInitMaxC3 = uint32(4096)
adcGndOffsetCompC3 = uint32(0)
adcCalTimesC3 = 15
adcCalRtcMagicC3 = uint32(0xADC1C401)
adcCalInitMinC3 = uint32(1000)
adcCalInitMaxC3 = uint32(4096)
)
type adcSelfCalibration struct {
digiRefMv uint32
}
// calibrate sets ADC1/ADC2 init code from RTC or runs self-calibration (GND).
// selfCalibrate sets ADC1/ADC2 init code from RTC or runs self-calibration (GND).
// eFuse is not used: on ESP32-C3 the ADC calibration fields in BLK2 are often unprogrammed.
func (c *adcSelfCalibration) calibrate() {
reg := regI2C{}
func adcSelfCalibrate() {
reg := newRegI2C()
reg.sarEnable()
var adc1Code uint32
if saved, ok := c.restoreFromRTC(); ok {
if saved, ok := restoreFromRTC(); ok {
adc1Code = saved
} else {
c.calSetupADC1()
reg.adc1CalibrationInit(0)
reg.adc1CalibrationPrepare(0)
adc1Code = c.calibrateUnit(reg, 0, c.readADC1)
c.saveToRTC(adc1Code)
reg.adc1CalibrationFinish(0)
calSetupADC1()
reg.calibrationInit(0)
reg.calibrationPrepare(0)
adc1Code = reg.calibrateBinarySearch(0, adcCalTimesC3, readADC1)
if adc1Code < adcCalInitMinC3 {
adc1Code = adcCalInitMinC3
}
if adc1Code > adcCalInitMaxC3 {
adc1Code = adcCalInitMaxC3
}
saveToRTC(adc1Code)
reg.calibrationFinish(0)
}
c.applyADC1Code(reg, adc1Code)
c.applyADC2Code(reg, adc1Code)
applyADC1Code(reg, adc1Code)
applyADC2Code(reg, adc1Code)
}
// calSetupADC1 configures APB_SARADC for oneshot sampling on ADC1 channel 0
// with fixed attenuation. This is used only during selfcalibration.
func (c *adcSelfCalibration) calSetupADC1() {
func calSetupADC1() {
esp.APB_SARADC.SetONETIME_SAMPLE_SARADC_ONETIME_ATTEN(atten11dB)
esp.APB_SARADC.SetONETIME_SAMPLE_SARADC_ONETIME_CHANNEL(0)
esp.APB_SARADC.SetONETIME_SAMPLE_SARADC1_ONETIME_SAMPLE(1)
@@ -132,7 +132,7 @@ func (c *adcSelfCalibration) calSetupADC1() {
// calSetupADC2 configures APB_SARADC for oneshot sampling on ADC2 (GPIO5, ch 0).
// On C3, onetime_channel = (unit<<3)|channel → ADC2 ch0 = 8.
func (c *adcSelfCalibration) calSetupADC2() {
func calSetupADC2() {
esp.APB_SARADC.SetONETIME_SAMPLE_SARADC_ONETIME_ATTEN(atten11dB)
esp.APB_SARADC.SetONETIME_SAMPLE_SARADC_ONETIME_CHANNEL(8) // (1<<3)|0 for ADC2
esp.APB_SARADC.SetARB_CTRL_ADC_ARB_APB_FORCE(1)
@@ -142,7 +142,7 @@ func (c *adcSelfCalibration) calSetupADC2() {
// readADC1 performs a single ADC1 conversion using the APB_SARADC
// oneshot path and returns the raw 12bit result (0..4095).
func (c *adcSelfCalibration) readADC1() uint32 {
func readADC1() uint32 {
esp.APB_SARADC.SetINT_CLR_APB_SARADC1_DONE_INT_CLR(1)
esp.APB_SARADC.SetONETIME_SAMPLE_SARADC_ONETIME_START(0)
esp.APB_SARADC.SetONETIME_SAMPLE_SARADC_ONETIME_START(1)
@@ -154,7 +154,7 @@ func (c *adcSelfCalibration) readADC1() uint32 {
}
// readADC2 performs a single ADC2 conversion and returns the raw 12bit result (0..4095).
func (c *adcSelfCalibration) readADC2() uint32 {
func readADC2() uint32 {
esp.APB_SARADC.SetINT_CLR_APB_SARADC2_DONE_INT_CLR(1)
esp.APB_SARADC.SetONETIME_SAMPLE_SARADC_ONETIME_START(0)
esp.APB_SARADC.SetONETIME_SAMPLE_SARADC_ONETIME_START(1)
@@ -167,7 +167,7 @@ func (c *adcSelfCalibration) readADC2() uint32 {
return uint32(raw)
}
func (c *adcSelfCalibration) restoreFromRTC() (uint32, bool) {
func restoreFromRTC() (uint32, bool) {
if esp.RTC_CNTL.GetSTORE0() != adcCalRtcMagicC3 {
return 0, false
}
@@ -178,7 +178,7 @@ func (c *adcSelfCalibration) restoreFromRTC() (uint32, bool) {
return code, true
}
func (c *adcSelfCalibration) saveToRTC(code uint32) {
func saveToRTC(code uint32) {
if code < adcCalInitMinC3 || code > adcCalInitMaxC3 {
return
}
@@ -187,228 +187,19 @@ func (c *adcSelfCalibration) saveToRTC(code uint32) {
}
// applyADC1Code sets ADC1 init code and finishes calibration.
func (c *adcSelfCalibration) applyADC1Code(reg regI2C, code uint32) {
c.calSetupADC1()
reg.adc1CalibrationInit(0)
reg.adc1CalibrationPrepare(0)
reg.adc1SetCalibrationParam(0, code)
reg.adc1CalibrationFinish(0)
func applyADC1Code(reg regI2C, code uint32) {
calSetupADC1()
reg.calibrationInit(0)
reg.calibrationPrepare(0)
reg.setCalibrationParam(0, code)
reg.calibrationFinish(0)
}
// applyADC2Code sets ADC2 init code and finishes calibration. On C3 eFuse V1
// there is no separate ADC2 calibration; IDF uses ADC1 init code for both units.
func (c *adcSelfCalibration) applyADC2Code(reg regI2C, code uint32) {
reg.adc1CalibrationInit(1)
reg.adc1CalibrationPrepare(1)
reg.adc1SetCalibrationParam(1, code)
reg.adc1CalibrationFinish(1)
}
func (c *adcSelfCalibration) calibrateUnit(reg regI2C, adcN uint8, readADC func() uint32) uint32 {
var codeList [adcCalTimesC3]uint32
var codeSum uint32
for rpt := 0; rpt < adcCalTimesC3; rpt++ {
codeH := adcCalOffsetRangeC3
codeL := uint32(0)
chkCode := (codeH + codeL) / 2
reg.adc1SetCalibrationParam(adcN, chkCode)
selfCal := readADC()
for codeH-codeL > 1 {
if selfCal == 0 {
codeH = chkCode
} else {
codeL = chkCode
}
chkCode = (codeH + codeL) / 2
reg.adc1SetCalibrationParam(adcN, chkCode)
selfCal = readADC()
if codeH-codeL == 1 {
chkCode++
reg.adc1SetCalibrationParam(adcN, chkCode)
selfCal = readADC()
}
}
codeList[rpt] = chkCode
codeSum += chkCode
}
codeL := codeList[0]
codeH := codeList[0]
for i := 0; i < adcCalTimesC3; i++ {
if codeList[i] < codeL {
codeL = codeList[i]
}
if codeList[i] > codeH {
codeH = codeList[i]
}
}
excluded := codeH + codeL
remaining := codeSum - excluded
finalCode := remaining / (adcCalTimesC3 - 2)
if remaining%(adcCalTimesC3-2) >= 4 {
finalCode++
}
if finalCode < adcCalInitMinC3 {
finalCode = adcCalInitMinC3
}
if finalCode > adcCalInitMaxC3 {
finalCode = adcCalInitMaxC3
}
reg.adc1SetCalibrationParam(adcN, finalCode)
return finalCode
}
// regi2c
// regI2C on ESP32C3 exposes the internal analog I2C bus that controls
// SAR ADC trim registers. Constants below mirror the layout from
// ESPIDF's soc/regi2c_saradc.h and TRM (I2C_RTC_CONFIG2 block).
const (
// i2cSarADC/i2cSarADCHostID select the SAR ADC block on the internal bus.
i2cSarADC = uint8(0x69)
i2cSarADCHostID = uint8(0)
// adc*_Dref* define the DREF (reference) bitfields for ADC1/ADC2.
adc1DrefAddr = uint8(0x2)
adc1DrefMSB = uint8(6)
adc1DrefLSB = uint8(4)
adc2DrefAddr = uint8(0x5)
adc2DrefMSB = uint8(6)
adc2DrefLSB = uint8(4)
// adc*_EncalGnd* control ENCAL_GND: route internal ground to ADC input
// during selfcalibration so that the pin is effectively disconnected.
adc1EncalGndAddr = uint8(0x7)
adc1EncalGndMSB = uint8(5)
adc1EncalGndLSB = uint8(5)
adc2EncalGndAddr = uint8(0x7)
adc2EncalGndMSB = uint8(7)
adc2EncalGndLSB = uint8(7)
// adc*_InitCode* hold the INIT_CODE (offset) that hardware uses to
// compensate ADC1/ADC2 offset error.
adc1InitCodeHighAddr = uint8(0x1)
adc1InitCodeHighMSB = uint8(3)
adc1InitCodeHighLSB = uint8(0)
adc1InitCodeLowAddr = uint8(0x0)
adc1InitCodeLowMSB = uint8(7)
adc1InitCodeLowLSB = uint8(0)
adc2InitCodeHighAddr = uint8(0x4)
adc2InitCodeHighMSB = uint8(3)
adc2InitCodeHighLSB = uint8(0)
adc2InitCodeLowAddr = uint8(0x3)
adc2InitCodeLowMSB = uint8(7)
adc2InitCodeLowLSB = uint8(0)
// ANA_CONFIG/ANA_CONFIG2: enable analog SAR I2C domain before regI2C access.
anaConfigReg = uintptr(0x6000E044)
i2cSarEnMask = uint32(1 << 18)
anaConfig2Reg = uintptr(0x6000E048)
anaSarCfg2En = uint32(1 << 16)
// I2C_RTC_CONFIG2 master control register used by regI2C operations.
i2cMstCtrlHost = uintptr(0x6000E000)
i2cMstBusyBit = uint32(1 << 25)
i2cMstWrCntl = uint32(1 << 24)
i2cMstDataMask = uint32(0xFF << 16)
i2cMstDataShift = 16
i2cMstTimeout = 10000
)
type regI2C struct{}
// sarEnable enables the SAR analog I2C domain before any regI2C access.
func (r *regI2C) sarEnable() {
cfg := (*volatile.Register32)(unsafe.Pointer(anaConfigReg))
cfg2 := (*volatile.Register32)(unsafe.Pointer(anaConfig2Reg))
esp.RTC_CNTL.SetANA_CONF_SAR_I2C_PU(1)
cfg.Set(cfg.Get() &^ i2cSarEnMask)
cfg2.Set(cfg2.Get() | anaSarCfg2En)
}
// adc1CalibrationInit sets DREF for the selected ADC unit
// before running the selfcalibration procedure.
func (r *regI2C) adc1CalibrationInit(adcN uint8) {
if adcN == 0 {
r.writeMask(i2cSarADC, i2cSarADCHostID, adc1DrefAddr, adc1DrefMSB, adc1DrefLSB, 1)
} else {
r.writeMask(i2cSarADC, i2cSarADCHostID, adc2DrefAddr, adc2DrefMSB, adc2DrefLSB, 1)
}
}
// adc1CalibrationPrepare enables ENCAL_GND so that the ADC input
// is internally shorted to ground during selfcalibration.
func (r *regI2C) adc1CalibrationPrepare(adcN uint8) {
if adcN == 0 {
r.writeMask(i2cSarADC, i2cSarADCHostID, adc1EncalGndAddr, adc1EncalGndMSB, adc1EncalGndLSB, 1)
} else {
r.writeMask(i2cSarADC, i2cSarADCHostID, adc2EncalGndAddr, adc2EncalGndMSB, adc2EncalGndLSB, 1)
}
}
// adc1CalibrationFinish clears ENCAL_GND and reconnects the ADC
// input back to the external pad after selfcalibration.
func (r *regI2C) adc1CalibrationFinish(adcN uint8) {
if adcN == 0 {
r.writeMask(i2cSarADC, i2cSarADCHostID, adc1EncalGndAddr, adc1EncalGndMSB, adc1EncalGndLSB, 0)
} else {
r.writeMask(i2cSarADC, i2cSarADCHostID, adc2EncalGndAddr, adc2EncalGndMSB, adc2EncalGndLSB, 0)
}
}
// adc1SetCalibrationParam writes the INIT_CODE (offset trim) for
// the selected ADC unit using the regI2C bitfields.
func (r *regI2C) adc1SetCalibrationParam(adcN uint8, param uint32) {
msb := uint8(param >> 8)
lsb := uint8(param & 0xFF)
if adcN == 0 {
r.writeMask(i2cSarADC, i2cSarADCHostID, adc1InitCodeHighAddr, adc1InitCodeHighMSB, adc1InitCodeHighLSB, msb)
r.writeMask(i2cSarADC, i2cSarADCHostID, adc1InitCodeLowAddr, adc1InitCodeLowMSB, adc1InitCodeLowLSB, lsb)
} else {
r.writeMask(i2cSarADC, i2cSarADCHostID, adc2InitCodeHighAddr, adc2InitCodeHighMSB, adc2InitCodeHighLSB, msb)
r.writeMask(i2cSarADC, i2cSarADCHostID, adc2InitCodeLowAddr, adc2InitCodeLowMSB, adc2InitCodeLowLSB, lsb)
}
}
// waitIdle polls the REGI2C master BUSY bit until it clears or the
// simple software timeout expires. This matches the busywait helper
// used in ESPIDF's regi2c_ctrl.c.
func (r *regI2C) waitIdle(reg *volatile.Register32) bool {
for i := 0; i < i2cMstTimeout; i++ {
if reg.Get()&i2cMstBusyBit == 0 {
return true
}
}
return false
}
// writeMask is a software implementation of REGI2C_WRITE_MASK macro:
// 1. select block + regAddr,
// 2. read current byte,
// 3. update only [msb:lsb] bitfield,
// 4. write it back via internal I2C master.
func (r *regI2C) writeMask(block, hostID, regAddr, msb, lsb, data uint8) {
if hostID != i2cSarADCHostID {
return
}
reg := (*volatile.Register32)(unsafe.Pointer(i2cMstCtrlHost))
if !r.waitIdle(reg) {
return
}
reg.Set(uint32(block) | uint32(regAddr)<<8)
if !r.waitIdle(reg) {
return
}
cur := (reg.Get() & i2cMstDataMask) >> i2cMstDataShift
mask := uint32(1<<(msb-lsb+1)-1) << lsb
cur &^= mask
cur |= uint32(data&(1<<(msb-lsb+1)-1)) << lsb
reg.Set(uint32(block) | uint32(regAddr)<<8 | i2cMstWrCntl | (cur<<i2cMstDataShift)&i2cMstDataMask)
r.waitIdle(reg)
func applyADC2Code(reg regI2C, code uint32) {
reg.calibrationInit(1)
reg.calibrationPrepare(1)
reg.setCalibrationParam(1, code)
reg.calibrationFinish(1)
}
+34 -246
View File
@@ -25,6 +25,9 @@ import (
"unsafe"
)
// newRegI2C returns the regI2C configured for ESP32-S3: hostID=1, drefInit=4.
func newRegI2C() regI2C { return regI2C{hostID: 1, drefInit: 4} }
var adcDigiRefMv uint32
func InitADC() {
@@ -85,9 +88,8 @@ func InitADC() {
esp.APB_SARADC.SetFILTER_CTRL1_FILTER_FACTOR0(0)
esp.APB_SARADC.SetFILTER_CTRL1_FILTER_FACTOR1(0)
adcCal := adcCalibration{}
adcCal.calibrate()
adcDigiRefMv = adcCal.getDigiRef()
adcSelfCalibrate()
adcDigiRefMv = getDigiRef()
}
const (
@@ -115,7 +117,6 @@ func (a ADC) Configure(config ADCConfig) error {
return errors.New("invalid ADC pin for ESP32-S3")
}
a.Pin.Configure(PinConfig{Mode: PinAnalog})
InitADC()
return nil
}
@@ -189,11 +190,11 @@ func (a ADC) GetVoltage() (raw uint32, v float64) {
//
// Mapping to ESP-IDF (adc_hal_common.c, hal/esp32s3/adc_ll.h):
// - adc_hal_self_calibration() → ADCSelfCalibrate()
// - adc_ll_calibration_init() → regI2C.ADC1CalibrationInit (DREF=4);
// - adc_ll_calibration_init() → regI2C.calibrationInit (DREF=4);
// in IDF it is not called from self_cal, we call it explicitly.
// - adc_ll_calibration_prepare() → SarEnable + ADC1CalibrationPrepare (ENCAL_GND=1)
// - adc_ll_calibration_finish() → ADC1CalibrationFinish (ENCAL_GND=0)
// - adc_ll_set_calibration_param() → ADC1SetCalibrationParam()
// - adc_ll_calibration_prepare() → SarEnable + calibrationPrepare (ENCAL_GND=1)
// - adc_ll_calibration_finish() → calibrationFinish (ENCAL_GND=0)
// - adc_ll_set_calibration_param() → setCalibrationParam()
// - read_cal_channel() → adcCalibration.readADC1():
// wait for meas_status==0, start 0→1, wait done, read data
// (similar to adc_oneshot_ll_start + get_raw_result).
@@ -209,7 +210,6 @@ func (a ADC) GetVoltage() (raw uint32, v float64) {
const (
adcCalTimes = 10
adcCalOffsetMax = uint32(4096)
adcCalRtcMagic = uint32(0xADC1C401)
adcCalInitMin = uint32(2000)
adcCalInitMax = uint32(3900)
@@ -220,44 +220,40 @@ const (
// adcCalibration encapsulates the self-calibration flow for ADC1
// and remembers per-chip calibration data (such as DIGI_REF) when it is
// available from eFuse.
type adcCalibration struct {
digiRefMv uint32
}
func (c *adcCalibration) calibrate() {
reg := regI2C{}
func adcSelfCalibrate() {
reg := newRegI2C()
f := fuse{}
if vref, ok := f.adc1DigiRefAtten3(); ok {
c.digiRefMv = vref
adcDigiRefMv = vref
}
if saved, ok := c.restoreFromRTC(); ok {
if saved, ok := restoreFromRTC(); ok {
reg.sarEnable()
reg.adc1CalibrationInit(0)
c.adc1CalibrateHigh(reg, saved)
reg.calibrationInit(0)
adc1CalibrateHigh(reg, saved)
return
}
initCode, useEfuse := f.adc1InitCodeAtten3()
c.adc1CalibrationSetup(reg)
adc1CalibrationSetup(reg)
if useEfuse {
c.saveToRTC(initCode)
c.adc1CalibrateHigh(reg, initCode)
saveToRTC(initCode)
adc1CalibrateHigh(reg, initCode)
return
}
finalCode := c.adc1CalibrateLow(reg)
c.saveToRTC(finalCode)
c.adc1CalibrateHigh(reg, finalCode)
finalCode := reg.calibrateBinarySearch(0, adcCalTimes, readADC1)
saveToRTC(finalCode)
adc1CalibrateHigh(reg, finalCode)
}
func (c *adcCalibration) getDigiRef() uint32 {
return c.digiRefMv
func getDigiRef() uint32 {
return adcDigiRefMv
}
func (c *adcCalibration) adc1CalibrationSetup(reg regI2C) {
func adc1CalibrationSetup(reg regI2C) {
reg.sarEnable()
esp.SENS.SetSAR_MEAS1_MUX_SAR1_DIG_FORCE(0)
@@ -268,67 +264,17 @@ func (c *adcCalibration) adc1CalibrationSetup(reg regI2C) {
esp.SENS.SetSAR_MEAS1_CTRL2_MEAS1_START_FORCE(1)
esp.SENS.SetSAR_MEAS1_CTRL2_SAR1_EN_PAD_FORCE(1)
reg.adc1CalibrationInit(0)
reg.adc1CalibrationPrepare(0)
reg.calibrationInit(0)
reg.calibrationPrepare(0)
}
func (c *adcCalibration) adc1CalibrateLow(reg regI2C) uint32 {
var codeList [adcCalTimes]uint32
var codeSum uint32
for rpt := 0; rpt < adcCalTimes; rpt++ {
codeH := adcCalOffsetMax
codeL := uint32(0)
chkCode := (codeH + codeL) / 2
reg.adc1SetCalibrationParam(0, chkCode)
selfCal := c.readADC1()
for codeH-codeL > 1 {
if selfCal == 0 {
codeH = chkCode
} else {
codeL = chkCode
}
chkCode = (codeH + codeL) / 2
reg.adc1SetCalibrationParam(0, chkCode)
selfCal = c.readADC1()
if codeH-codeL == 1 {
chkCode++
reg.adc1SetCalibrationParam(0, chkCode)
selfCal = c.readADC1()
}
}
codeList[rpt] = chkCode
codeSum += chkCode
}
codeL := codeList[0]
codeH := codeList[0]
for i := 0; i < adcCalTimes; i++ {
if codeList[i] < codeL {
codeL = codeList[i]
}
if codeList[i] > codeH {
codeH = codeList[i]
}
}
excluded := codeH + codeL
remaining := codeSum - excluded
finalCode := remaining / (adcCalTimes - 2)
if remaining%(adcCalTimes-2) >= 4 {
finalCode++
}
return finalCode
func adc1CalibrateHigh(reg regI2C, code uint32) {
reg.setCalibrationParam(0, code)
reg.calibrationFinish(0)
adc1StartWithPadForce()
}
func (c *adcCalibration) adc1CalibrateHigh(reg regI2C, code uint32) {
reg.adc1SetCalibrationParam(0, code)
reg.adc1CalibrationFinish(0)
c.adc1StartWithPadForce()
}
func (c *adcCalibration) adc1StartWithPadForce() {
func adc1StartWithPadForce() {
esp.SENS.SetSAR_MEAS1_CTRL2_SAR1_EN_PAD_FORCE(1)
esp.SENS.SetSAR_MEAS1_CTRL2_MEAS1_START_FORCE(1)
}
@@ -336,7 +282,7 @@ func (c *adcCalibration) adc1StartWithPadForce() {
// readADC1 performs one ADC1 conversion via RTC path (used during calibration).
// Internal GND is connected via ENCAL_GND, so the pin input is disconnected.
// Matches IDF: wait conversion idle (meas_status==0), then start 0→1, wait done, read data.
func (c *adcCalibration) readADC1() uint32 {
func readADC1() uint32 {
for esp.SENS.GetSAR_SLAVE_ADDR1_SAR_SARADC_MEAS_STATUS() != 0 {
}
esp.SENS.SetSAR_MEAS1_CTRL2_MEAS1_START_SAR(0)
@@ -346,7 +292,7 @@ func (c *adcCalibration) readADC1() uint32 {
return uint32(esp.SENS.GetSAR_MEAS1_CTRL2_MEAS1_DATA_SAR() & 0xfff)
}
func (c *adcCalibration) restoreFromRTC() (uint32, bool) {
func restoreFromRTC() (uint32, bool) {
if esp.RTC_CNTL.GetSTORE0() != adcCalRtcMagic {
return 0, false
}
@@ -357,169 +303,11 @@ func (c *adcCalibration) restoreFromRTC() (uint32, bool) {
return code, true
}
func (c *adcCalibration) saveToRTC(code uint32) {
func saveToRTC(code uint32) {
esp.RTC_CNTL.SetSTORE0(adcCalRtcMagic)
esp.RTC_CNTL.SetSTORE1(code)
}
// regI2C — internal I2C for SAR ADC (ESP32-S2 I2C_RTC_CONFIG2, reg 0x6000E000).
// Source: idf-source/components/soc/esp32s3/include/soc/regi2c_saradc.h
const (
// I2C_SAR_ADC / I2C_SAR_ADC_HOSTID in regi2c_saradc.h
i2cSarADC = uint8(0x69) // I2C_SAR_ADC
i2cSarADCHostID = uint8(1) // I2C_SAR_ADC_HOSTID
// ADC_SAR1_DREF_ADDR(_MSB/_LSB)
adc1DrefAddr = uint8(0x2) // ADC_SAR1_DREF_ADDR
adc1DrefMSB = uint8(6) // ADC_SAR1_DREF_ADDR_MSB
adc1DrefLSB = uint8(4) // ADC_SAR1_DREF_ADDR_LSB
// ADC_SAR2_DREF_ADDR(_MSB/_LSB)
adc2DrefAddr = uint8(0x5) // ADC_SAR2_DREF_ADDR
adc2DrefMSB = uint8(6) // ADC_SAR2_DREF_ADDR_MSB
adc2DrefLSB = uint8(4) // ADC_SAR2_DREF_ADDR_LSB
// ADC_SAR1_ENCAL_GND_ADDR(_MSB/_LSB)
adc1EncalGndAddr = uint8(0x7) // ADC_SAR1_ENCAL_GND_ADDR
adc1EncalGndMSB = uint8(5) // ADC_SAR1_ENCAL_GND_ADDR_MSB
adc1EncalGndLSB = uint8(5) // ADC_SAR1_ENCAL_GND_ADDR_LSB
// ADC_SAR2_ENCAL_GND_ADDR(_MSB/_LSB)
adc2EncalGndAddr = uint8(0x7) // ADC_SAR2_ENCAL_GND_ADDR
adc2EncalGndMSB = uint8(7) // ADC_SAR2_ENCAL_GND_ADDR_MSB
adc2EncalGndLSB = uint8(7) // ADC_SAR2_ENCAL_GND_ADDR_LSB
// ADC_SAR1_INITIAL_CODE_HIGH/LOW_ADDR(_MSB/_LSB)
adc1InitCodeHighAddr = uint8(0x1) // ADC_SAR1_INITIAL_CODE_HIGH_ADDR
adc1InitCodeHighMSB = uint8(3) // ADC_SAR1_INITIAL_CODE_HIGH_ADDR_MSB
adc1InitCodeHighLSB = uint8(0) // ADC_SAR1_INITIAL_CODE_HIGH_ADDR_LSB
adc1InitCodeLowAddr = uint8(0x0) // ADC_SAR1_INITIAL_CODE_LOW_ADDR
adc1InitCodeLowMSB = uint8(7) // ADC_SAR1_INITIAL_CODE_LOW_ADDR_MSB
adc1InitCodeLowLSB = uint8(0) // ADC_SAR1_INITIAL_CODE_LOW_ADDR_LSB
// ADC_SAR2_INITIAL_CODE_HIGH/LOW_ADDR(_MSB/_LSB)
adc2InitCodeHighAddr = uint8(0x4) // ADC_SAR2_INITIAL_CODE_HIGH_ADDR
adc2InitCodeHighMSB = uint8(3) // ADC_SAR2_INITIAL_CODE_HIGH_ADDR_MSB
adc2InitCodeHighLSB = uint8(0) // ADC_SAR2_INITIAL_CODE_HIGH_ADDR_LSB
adc2InitCodeLowAddr = uint8(0x3) // ADC_SAR2_INITIAL_CODE_LOW_ADDR
adc2InitCodeLowMSB = uint8(7) // ADC_SAR2_INITIAL_CODE_LOW_ADDR_MSB
adc2InitCodeLowLSB = uint8(0) // ADC_SAR2_INITIAL_CODE_LOW_ADDR_LSB
// Analog config registers for regI2C block (RTC/ANA config in TRM).
anaConfigReg = uintptr(0x6000E044)
i2cSarEnMask = uint32(1 << 18)
anaConfig2Reg = uintptr(0x6000E048)
anaSarCfg2En = uint32(1 << 16)
// REGI2C master control register and helper masks.
i2cMstCtrlHost1 = uintptr(0x6000E000)
i2cMstBusyBit = uint32(1 << 25)
i2cMstWrCntlBit = uint32(1 << 24)
i2cMstDataMask = uint32(0xFF << 16)
i2cMstDataShift = 16
i2cMstBusyTimeout = 10000
)
type regI2C struct{}
// waitIdle mimics the IDF regi2c busy-wait helper (see regi2c_ctrl.c).
// It polls the REGI2C master control register until the BUSY bit clears
// or a small timeout expires, to avoid writing while a previous transfer
// is still in progress.
func (r *regI2C) waitIdle(reg *volatile.Register32) bool {
for i := 0; i < i2cMstBusyTimeout; i++ {
if reg.Get()&i2cMstBusyBit == 0 {
return true
}
}
return false
}
// writeMask is a software implementation of the REGI2C_WRITE_MASK macro
// from IDF (see soc/regi2c_saradc.h). It:
// - selects the regI2C SAR ADC block + register address,
// - reads the current byte,
// - updates only the [msb:lsb] bitfield,
// - writes the new value back via the internal I2C master.
func (r *regI2C) writeMask(block, hostID, regAddr, msb, lsb, data uint8) {
if hostID != i2cSarADCHostID {
return
}
reg := (*volatile.Register32)(unsafe.Pointer(i2cMstCtrlHost1))
if !r.waitIdle(reg) {
return
}
reg.Set(uint32(block) | uint32(regAddr)<<8)
if !r.waitIdle(reg) {
return
}
cur := (reg.Get() & i2cMstDataMask) >> i2cMstDataShift
mask := uint32(1<<(msb-lsb+1)-1) << lsb
cur &^= mask
cur |= uint32(data&(1<<(msb-lsb+1)-1)) << lsb
reg.Set(uint32(block) | uint32(regAddr)<<8 | i2cMstWrCntlBit | (cur<<i2cMstDataShift)&i2cMstDataMask)
r.waitIdle(reg)
}
// sarEnable enables the analog SAR I2C domain before any regI2C access,
// matching the prologue in adc_ll_calibration_prepare() (sets ANA_SAR_CFG2_EN).
func (r *regI2C) sarEnable() {
cfg := (*volatile.Register32)(unsafe.Pointer(anaConfigReg))
cfg2 := (*volatile.Register32)(unsafe.Pointer(anaConfig2Reg))
esp.RTC_CNTL.SetANA_CONF_SAR_I2C_PU(1)
cfg.Set(cfg.Get() &^ i2cSarEnMask)
cfg2.Set(cfg2.Get() | anaSarCfg2En)
}
// adc1CalibrationInit corresponds to adc_ll_calibration_init() for ESP32-S3:
// it sets the DREF field to 4 for the selected ADC unit, which is the
// reference index used by Espressif's calibration flow.
func (r *regI2C) adc1CalibrationInit(adcN uint8) {
if adcN == 0 {
r.writeMask(i2cSarADC, i2cSarADCHostID, adc1DrefAddr, adc1DrefMSB, adc1DrefLSB, 4)
} else {
r.writeMask(i2cSarADC, i2cSarADCHostID, adc2DrefAddr, adc2DrefMSB, adc2DrefLSB, 4)
}
}
// adc1CalibrationPrepare corresponds to the ENCAL_GND part of
// adc_ll_calibration_prepare(): it temporarily routes the internal
// ground reference into the SAR input so that self-calibration can
// measure offset with the pin disconnected.
func (r *regI2C) adc1CalibrationPrepare(adcN uint8) {
if adcN == 0 {
r.writeMask(i2cSarADC, i2cSarADCHostID, adc1EncalGndAddr, adc1EncalGndMSB, adc1EncalGndLSB, 1)
} else {
r.writeMask(i2cSarADC, i2cSarADCHostID, adc2EncalGndAddr, adc2EncalGndMSB, adc2EncalGndLSB, 1)
}
}
// adc1CalibrationFinish corresponds to adc_ll_calibration_finish():
// it clears ENCAL_GND so that ADC input is again connected to the pad.
func (r *regI2C) adc1CalibrationFinish(adcN uint8) {
if adcN == 0 {
r.writeMask(i2cSarADC, i2cSarADCHostID, adc1EncalGndAddr, adc1EncalGndMSB, adc1EncalGndLSB, 0)
} else {
r.writeMask(i2cSarADC, i2cSarADCHostID, adc2EncalGndAddr, adc2EncalGndMSB, adc2EncalGndLSB, 0)
}
}
// adc1SetCalibrationParam corresponds to adc_ll_set_calibration_param():
// it writes the 9-bit initial code (offset) into the high/low INIT_CODE
// regI2C registers for the selected ADC unit.
func (r *regI2C) adc1SetCalibrationParam(adcN uint8, param uint32) {
msb := uint8(param >> 8)
lsb := uint8(param & 0xFF)
if adcN == 0 {
r.writeMask(i2cSarADC, i2cSarADCHostID, adc1InitCodeHighAddr, adc1InitCodeHighMSB, adc1InitCodeHighLSB, msb)
r.writeMask(i2cSarADC, i2cSarADCHostID, adc1InitCodeLowAddr, adc1InitCodeLowMSB, adc1InitCodeLowLSB, lsb)
} else {
r.writeMask(i2cSarADC, i2cSarADCHostID, adc2InitCodeHighAddr, adc2InitCodeHighMSB, adc2InitCodeHighLSB, msb)
r.writeMask(i2cSarADC, i2cSarADCHostID, adc2InitCodeLowAddr, adc2InitCodeLowMSB, adc2InitCodeLowLSB, lsb)
}
}
// fuse
const (
// Base address for eFuse controller (EFUSE_BLKx region in TRM).
+235
View File
@@ -0,0 +1,235 @@
//go:build esp32s3 || (esp32c3 && !m5stamp_c3)
// Shared regI2C-based ADC calibration helpers for ESP32-S3 and ESP32-C3.
//
// The internal I2C bus ("regI2C") and SAR ADC trim register layout are
// identical across both chips; chip-specific differences (host ID, DREF
// init value, calibration iterations) are captured in the regI2C struct
// fields, keeping each target file free of duplicated low-level code.
package machine
import (
"device/esp"
"runtime/volatile"
"unsafe"
)
// regI2C wraps the internal I2C bus used for SAR ADC calibration registers.
// Fields hold chip-specific parameters that differ between ESP32-S3 and ESP32-C3.
type regI2C struct {
// hostID is the I2C_SAR_ADC_HOSTID (1 for ESP32-S3, 0 for ESP32-C3).
hostID uint8
// drefInit is the DREF reference value written during calibrationInit
// (4 for ESP32-S3, 1 for ESP32-C3).
drefInit uint8
}
// SAR ADC I2C register layout constants shared across ESP32-S3 and ESP32-C3.
// Source: ESP-IDF soc/regi2c_saradc.h
const (
// i2cSarADC is the I2C_SAR_ADC block address on the internal bus.
i2cSarADC = uint8(0x69)
// DREF (reference) bitfields for ADC1 and ADC2.
adc1DrefAddr = uint8(0x2)
adc1DrefMSB = uint8(6)
adc1DrefLSB = uint8(4)
adc2DrefAddr = uint8(0x5)
adc2DrefMSB = uint8(6)
adc2DrefLSB = uint8(4)
// ENCAL_GND: routes internal ground to ADC input during self-calibration.
adc1EncalGndAddr = uint8(0x7)
adc1EncalGndMSB = uint8(5)
adc1EncalGndLSB = uint8(5)
adc2EncalGndAddr = uint8(0x7)
adc2EncalGndMSB = uint8(7)
adc2EncalGndLSB = uint8(7)
// INIT_CODE (offset) high/low for ADC1 and ADC2.
adc1InitCodeHighAddr = uint8(0x1)
adc1InitCodeHighMSB = uint8(3)
adc1InitCodeHighLSB = uint8(0)
adc1InitCodeLowAddr = uint8(0x0)
adc1InitCodeLowMSB = uint8(7)
adc1InitCodeLowLSB = uint8(0)
adc2InitCodeHighAddr = uint8(0x4)
adc2InitCodeHighMSB = uint8(3)
adc2InitCodeHighLSB = uint8(0)
adc2InitCodeLowAddr = uint8(0x3)
adc2InitCodeLowMSB = uint8(7)
adc2InitCodeLowLSB = uint8(0)
// ANA_CONFIG / ANA_CONFIG2: enable analog SAR I2C domain.
anaConfigReg = uintptr(0x6000E044)
i2cSarEnMask = uint32(1 << 18)
anaConfig2Reg = uintptr(0x6000E048)
anaSarCfg2En = uint32(1 << 16)
// REGI2C master control register and helper masks.
i2cMstCtrlReg = uintptr(0x6000E000)
i2cMstBusyBit = uint32(1 << 25)
i2cMstWrCntlBit = uint32(1 << 24)
i2cMstDataMask = uint32(0xFF << 16)
i2cMstDataShift = 16
i2cMstBusyTimeout = 10000
// adcCalOffsetRange is the binary search upper bound (12-bit full scale).
adcCalOffsetRange = uint32(4096)
// adcCalMaxIterations is the maximum number of calibration iterations
// supported by calibrateBinarySearch. Must be >= max(S3=10, C3=15).
adcCalMaxIterations = 16
)
// waitIdle polls the REGI2C master BUSY bit until it clears or a
// timeout expires, matching the busy-wait helper in ESP-IDF's regi2c_ctrl.c.
func (r regI2C) waitIdle(reg *volatile.Register32) bool {
for i := 0; i < i2cMstBusyTimeout; i++ {
if reg.Get()&i2cMstBusyBit == 0 {
return true
}
}
return false
}
// writeMask is a software implementation of the IDF REGI2C_WRITE_MASK macro.
// It reads the current byte at regAddr on the SAR ADC I2C block, updates
// only the [msb:lsb] bitfield, and writes it back via the internal I2C master.
func (r regI2C) writeMask(regAddr, msb, lsb, data uint8) {
reg := (*volatile.Register32)(unsafe.Pointer(i2cMstCtrlReg))
if !r.waitIdle(reg) {
return
}
reg.Set(uint32(i2cSarADC) | uint32(regAddr)<<8)
if !r.waitIdle(reg) {
return
}
cur := (reg.Get() & i2cMstDataMask) >> i2cMstDataShift
mask := uint32(1<<(msb-lsb+1)-1) << lsb
cur &^= mask
cur |= uint32(data&(1<<(msb-lsb+1)-1)) << lsb
reg.Set(uint32(i2cSarADC) | uint32(regAddr)<<8 | i2cMstWrCntlBit | (cur<<i2cMstDataShift)&i2cMstDataMask)
r.waitIdle(reg)
}
// sarEnable enables the analog SAR I2C domain before any regI2C access,
// matching the prologue in adc_ll_calibration_prepare().
func (r regI2C) sarEnable() {
cfg := (*volatile.Register32)(unsafe.Pointer(anaConfigReg))
cfg2 := (*volatile.Register32)(unsafe.Pointer(anaConfig2Reg))
esp.RTC_CNTL.SetANA_CONF_SAR_I2C_PU(1)
cfg.Set(cfg.Get() &^ i2cSarEnMask)
cfg2.Set(cfg2.Get() | anaSarCfg2En)
}
// calibrationInit sets the DREF reference for the selected ADC unit to
// the chip-specific init value before running self-calibration.
// Corresponds to adc_ll_calibration_init() in ESP-IDF.
func (r regI2C) calibrationInit(adcN uint8) {
if adcN == 0 {
r.writeMask(adc1DrefAddr, adc1DrefMSB, adc1DrefLSB, r.drefInit)
} else {
r.writeMask(adc2DrefAddr, adc2DrefMSB, adc2DrefLSB, r.drefInit)
}
}
// calibrationPrepare enables ENCAL_GND so that the ADC input is
// internally shorted to ground during self-calibration.
// Corresponds to the ENCAL_GND part of adc_ll_calibration_prepare().
func (r regI2C) calibrationPrepare(adcN uint8) {
if adcN == 0 {
r.writeMask(adc1EncalGndAddr, adc1EncalGndMSB, adc1EncalGndLSB, 1)
} else {
r.writeMask(adc2EncalGndAddr, adc2EncalGndMSB, adc2EncalGndLSB, 1)
}
}
// calibrationFinish clears ENCAL_GND to reconnect the ADC input to the
// external pad after self-calibration.
// Corresponds to adc_ll_calibration_finish() in ESP-IDF.
func (r regI2C) calibrationFinish(adcN uint8) {
if adcN == 0 {
r.writeMask(adc1EncalGndAddr, adc1EncalGndMSB, adc1EncalGndLSB, 0)
} else {
r.writeMask(adc2EncalGndAddr, adc2EncalGndMSB, adc2EncalGndLSB, 0)
}
}
// setCalibrationParam writes the INIT_CODE (offset trim) for the selected
// ADC unit via the regI2C bitfields.
// Corresponds to adc_ll_set_calibration_param() in ESP-IDF.
func (r regI2C) setCalibrationParam(adcN uint8, param uint32) {
msb := uint8(param >> 8)
lsb := uint8(param & 0xFF)
if adcN == 0 {
r.writeMask(adc1InitCodeHighAddr, adc1InitCodeHighMSB, adc1InitCodeHighLSB, msb)
r.writeMask(adc1InitCodeLowAddr, adc1InitCodeLowMSB, adc1InitCodeLowLSB, lsb)
} else {
r.writeMask(adc2InitCodeHighAddr, adc2InitCodeHighMSB, adc2InitCodeHighLSB, msb)
r.writeMask(adc2InitCodeLowAddr, adc2InitCodeLowMSB, adc2InitCodeLowLSB, lsb)
}
}
// calibrateBinarySearch runs the ADC self-calibration binary search loop.
// It performs 'iterations' rounds of binary search to find the optimal offset
// code, drops the min/max outliers, and returns the rounded mean of the
// remaining values. This matches adc_hal_self_calibration() in ESP-IDF.
//
// The readADC callback must perform a single conversion using the target's
// oneshot path (SENS or APB_SARADC) and return the raw 12-bit result.
// During calibration, ENCAL_GND is active so the ADC reads its internal ground.
func (r regI2C) calibrateBinarySearch(adcN uint8, iterations int, readADC func() uint32) uint32 {
if iterations > adcCalMaxIterations {
iterations = adcCalMaxIterations
}
var codeList [adcCalMaxIterations]uint32
var codeSum uint32
for rpt := 0; rpt < iterations; rpt++ {
codeH := adcCalOffsetRange
codeL := uint32(0)
chkCode := (codeH + codeL) / 2
r.setCalibrationParam(adcN, chkCode)
selfCal := readADC()
for codeH-codeL > 1 {
if selfCal == 0 {
codeH = chkCode
} else {
codeL = chkCode
}
chkCode = (codeH + codeL) / 2
r.setCalibrationParam(adcN, chkCode)
selfCal = readADC()
if codeH-codeL == 1 {
chkCode++
r.setCalibrationParam(adcN, chkCode)
selfCal = readADC()
}
}
codeList[rpt] = chkCode
codeSum += chkCode
}
// Drop min and max outliers, then average with IDF-style rounding.
codeMin := codeList[0]
codeMax := codeList[0]
for i := 0; i < iterations; i++ {
if codeList[i] < codeMin {
codeMin = codeList[i]
}
if codeList[i] > codeMax {
codeMax = codeList[i]
}
}
remaining := codeSum - codeMax - codeMin
divisor := uint32(iterations - 2)
finalCode := remaining / divisor
if remaining%divisor >= 4 {
finalCode++
}
return finalCode
}