From f9024378480f532d3f6d76bb5b643176c6b2dff5 Mon Sep 17 00:00:00 2001 From: deadprogram Date: Sun, 1 Mar 2026 23:21:34 +0100 Subject: [PATCH] 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 --- src/machine/machine_esp32c3_adc.go | 295 +++++------------------------ src/machine/machine_esp32s3_adc.go | 280 ++++----------------------- src/machine/machine_esp32xx_adc.go | 235 +++++++++++++++++++++++ 3 files changed, 312 insertions(+), 498 deletions(-) create mode 100644 src/machine/machine_esp32xx_adc.go diff --git a/src/machine/machine_esp32c3_adc.go b/src/machine/machine_esp32c3_adc.go index ec23637ed..a90866626 100644 --- a/src/machine/machine_esp32c3_adc.go +++ b/src/machine/machine_esp32c3_adc.go @@ -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 = GPIO0–GPIO4 (ch 0–4), ADC2 = GPIO5 (ch 0). ADC2 shares with Wi‑Fi; @@ -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 self‑calibration. -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 12‑bit 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 12‑bit 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 ESP32‑C3 exposes the internal analog I2C bus that controls -// SAR ADC trim registers. Constants below mirror the layout from -// ESP‑IDF'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 self‑calibration 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 self‑calibration 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 self‑calibration. -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 self‑calibration. -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 busy‑wait helper -// used in ESP‑IDF'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< 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<> 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). diff --git a/src/machine/machine_esp32xx_adc.go b/src/machine/machine_esp32xx_adc.go new file mode 100644 index 000000000..30fec86cb --- /dev/null +++ b/src/machine/machine_esp32xx_adc.go @@ -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<> 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 +}