esp32s3 + c3 ADC (#5231)

* save

* esp32s3: save

* adc

* worker on esp32s3

* worker after flash arduino

* save

* fix

* simple adc

* added adc

* esp32s3-adc: rm debug

* esp32s3-adc: clear

* last refactor

* linters

* esp32s3-adc: recover example

* esp32s3-adc: reuse fuse for esp32c3

* esp32s3-adc: refactor bugs

* esp32s3-adc: fix adc2 for esp32c3

* esp32s3-adc: group to adc files

* esp32s3-adc: revert changing board

* esp32s3-adc: recover example adc

* esp32s3-adc: fix edge values adc & added smoketests

* esp32s3-adc: rename methods

* esp32s3-adc: extends adc tests

* esp32s3-adc: drop debug

* esp32s3-adc: added ADCX const

* esp32s3-adc: change adc tests

* esp32s3-adc: added comment for esp32c3

* esp32s3-adc: drop debug empty loops

* esp32s3-adc: drop duplicate gpio

* esp32s3-adc: change return values to 0..65520
This commit is contained in:
Dima
2026-03-01 23:45:01 +02:00
committed by deadprogram
parent 3c590e36ad
commit 6bb6ea2432
7 changed files with 1123 additions and 31 deletions
+6
View File
@@ -934,6 +934,8 @@ ifneq ($(XTENSA), 0)
@$(MD5SUM) test.bin
$(TINYGO) build -size short -o test.bin -target=xiao-esp32s3 examples/pwm
@$(MD5SUM) test.bin
$(TINYGO) build -size short -o test.bin -target=xiao-esp32s3 examples/adc
@$(MD5SUM) test.bin
# esp32s3-wroom1
$(TINYGO) build -size short -o test.bin -target=esp32s3-wroom1 examples/blinkm
@@ -942,6 +944,8 @@ ifneq ($(XTENSA), 0)
@$(MD5SUM) test.bin
$(TINYGO) build -size short -o test.bin -target=esp32s3-wroom1 examples/pwm
@$(MD5SUM) test.bin
$(TINYGO) build -size short -o test.bin -target=xiao-esp32s3 examples/adc
@$(MD5SUM) test.bin
endif
# esp32c3-supermini
$(TINYGO) build -size short -o test.bin -target=esp32c3-supermini examples/blinky1
@@ -952,6 +956,8 @@ endif
@$(MD5SUM) test.bin
$(TINYGO) build -size short -o test.bin -target=esp32c3-supermini examples/pwm
@$(MD5SUM) test.bin
$(TINYGO) build -size short -o test.bin -target=esp32c3-supermini examples/adc
@$(MD5SUM) test.bin
$(TINYGO) build -size short -o test.bin -target=esp-c3-32s-kit examples/blinky1
@$(MD5SUM) test.bin
+2 -12
View File
@@ -5,25 +5,15 @@ import (
"time"
)
// This example assumes that an analog sensor such as a rotary dial is connected to pin ADC0.
// When the dial is turned past the midway point, the built-in LED will light up.
func main() {
machine.InitADC()
led := machine.LED
led.Configure(machine.PinConfig{Mode: machine.PinOutput})
sensor := machine.ADC{machine.ADC2}
sensor.Configure(machine.ADCConfig{})
for {
val := sensor.Get()
if val < 0x8000 {
led.Low()
} else {
led.High()
}
time.Sleep(time.Millisecond * 100)
println(val)
time.Sleep(time.Millisecond * 500)
}
}
-3
View File
@@ -30,9 +30,6 @@ const (
// ADC pins
const (
ADC0 Pin = ADC1_0
ADC1 Pin = ADC2_0
ADC1_0 Pin = IO0
ADC1_1 Pin = IO1
ADC1_2 Pin = IO2
+26 -11
View File
@@ -27,16 +27,29 @@ const (
PinInput
PinInputPullup
PinInputPulldown
PinAnalog
)
const (
GPIO0 Pin = 0
GPIO1 Pin = 1
GPIO2 Pin = 2
GPIO3 Pin = 3
GPIO4 Pin = 4
GPIO5 Pin = 5
GPIO6 Pin = 6
)
const (
ADC0 Pin = GPIO0
ADC1 Pin = GPIO1
ADC2 Pin = GPIO2
ADC3 Pin = GPIO3
ADC4 Pin = GPIO4
ADC5 Pin = GPIO5 // avoid when WiFi is used.
)
const (
GPIO0 Pin = 0
GPIO1 Pin = 1
GPIO2 Pin = 2
GPIO3 Pin = 3
GPIO4 Pin = 4
GPIO5 Pin = 5
GPIO6 Pin = 6
GPIO7 Pin = 7
GPIO8 Pin = 8
GPIO9 Pin = 9
@@ -76,13 +89,15 @@ func (p Pin) Configure(config PinConfig) {
const function = 1 // function 1 is GPIO for every pin
muxConfig |= function << esp.IO_MUX_GPIO_MCU_SEL_Pos
// Make this pin an input pin (always).
muxConfig |= esp.IO_MUX_GPIO_FUN_IE
// FUN_IE: disable for PinAnalog (high-Z for ADC)
if config.Mode != PinAnalog {
muxConfig |= esp.IO_MUX_GPIO_FUN_IE
}
// Set drive strength: 0 is lowest, 3 is highest.
muxConfig |= 2 << esp.IO_MUX_GPIO_FUN_DRV_Pos
// Select pull mode.
// Select pull mode (no pulls for PinAnalog).
if config.Mode == PinInputPullup {
muxConfig |= esp.IO_MUX_GPIO_FUN_WPU
} else if config.Mode == PinInputPulldown {
@@ -99,7 +114,7 @@ func (p Pin) Configure(config PinConfig) {
case PinOutput:
// Set the 'output enable' bit.
esp.GPIO.ENABLE_W1TS.Set(1 << p)
case PinInput, PinInputPullup, PinInputPulldown:
case PinInput, PinInputPullup, PinInputPulldown, PinAnalog:
// Clear the 'output enable' bit.
esp.GPIO.ENABLE_W1TC.Set(1 << p)
}
+414
View File
@@ -0,0 +1,414 @@
//go:build esp32c3 && !m5stamp_c3
package machine
import (
"device/esp"
"errors"
"runtime/volatile"
"unsafe"
)
const (
// ADC attenuation values for ESP32-C3 APB_SARADC.
// 0 dB : ~0 .. 1.1 V
// 11 dB : ~0 .. 3.3 V (matches typical VDD)
atten0dB = 0
atten11dB = 3
)
func InitADC() {
esp.SYSTEM.SetPERIP_RST_EN0_APB_SARADC_RST(1)
esp.SYSTEM.SetPERIP_CLK_EN0_APB_SARADC_CLK_EN(1)
esp.SYSTEM.SetPERIP_RST_EN0_APB_SARADC_RST(0)
esp.RTC_CNTL.SetANA_CONF_SAR_I2C_PU(1)
esp.RTC_CNTL.SetSENSOR_CTRL_FORCE_XPD_SAR(1)
esp.APB_SARADC.SetCTRL_SARADC_XPD_SAR_FORCE(1)
esp.APB_SARADC.SetFSM_WAIT_SARADC_XPD_WAIT(8)
esp.APB_SARADC.SetFSM_WAIT_SARADC_RSTB_WAIT(8)
esp.APB_SARADC.SetFSM_WAIT_SARADC_STANDBY_WAIT(100)
esp.APB_SARADC.SetCLKM_CONF_CLK_SEL(2)
esp.APB_SARADC.SetCLKM_CONF_CLKM_DIV_NUM(1)
esp.APB_SARADC.SetCLKM_CONF_CLKM_DIV_B(0)
esp.APB_SARADC.SetCLKM_CONF_CLKM_DIV_A(0)
esp.APB_SARADC.SetCLKM_CONF_CLK_EN(1)
var c adcSelfCalibration
c.calibrate()
}
// ESP32-C3: ADC1 = GPIO0GPIO4 (ch 04), ADC2 = GPIO5 (ch 0). ADC2 shares with WiFi;
// readings may be noisy when WiFi is active.
func (a ADC) Configure(config ADCConfig) error {
if a.Pin > 5 {
return errors.New("invalid ADC pin for ESP32-C3")
}
a.Pin.Configure(PinConfig{Mode: PinAnalog})
return nil
}
func (a ADC) Get() uint16 {
if a.Pin > 5 {
return 0
}
adc1 := a.Pin <= 4
esp.APB_SARADC.SetONETIME_SAMPLE_SARADC_ONETIME_ATTEN(atten11dB)
esp.APB_SARADC.SetINT_CLR_APB_SARADC1_DONE_INT_CLR(1)
esp.APB_SARADC.SetINT_CLR_APB_SARADC2_DONE_INT_CLR(1)
esp.APB_SARADC.SetONETIME_SAMPLE_SARADC_ONETIME_START(0)
var raw uint32
if adc1 {
esp.APB_SARADC.SetONETIME_SAMPLE_SARADC_ONETIME_CHANNEL(uint32(a.Pin))
esp.APB_SARADC.SetONETIME_SAMPLE_SARADC1_ONETIME_SAMPLE(1)
esp.APB_SARADC.SetONETIME_SAMPLE_SARADC_ONETIME_START(1)
for esp.APB_SARADC.GetINT_RAW_APB_SARADC1_DONE_INT_RAW() == 0 {
}
raw = esp.APB_SARADC.GetSAR1DATA_STATUS_APB_SARADC1_DATA()
esp.APB_SARADC.SetONETIME_SAMPLE_SARADC_ONETIME_START(0)
esp.APB_SARADC.SetONETIME_SAMPLE_SARADC1_ONETIME_SAMPLE(0)
} else {
// ADC2: GPIO5 = channel 0. Grant arbiter to ADC2 first, then set channel and start.
esp.APB_SARADC.SetONETIME_SAMPLE_SARADC1_ONETIME_SAMPLE(0)
esp.APB_SARADC.SetARB_CTRL_ADC_ARB_APB_FORCE(1)
esp.APB_SARADC.SetARB_CTRL_ADC_ARB_GRANT_FORCE(1)
esp.APB_SARADC.SetONETIME_SAMPLE_SARADC_ONETIME_CHANNEL(8) // (1<<3)|0 for ADC2 channel 0
esp.APB_SARADC.SetONETIME_SAMPLE_SARADC2_ONETIME_SAMPLE(1)
esp.APB_SARADC.SetONETIME_SAMPLE_SARADC_ONETIME_START(1)
for esp.APB_SARADC.GetINT_RAW_APB_SARADC2_DONE_INT_RAW() == 0 {
}
raw = esp.APB_SARADC.GetSAR2DATA_STATUS_APB_SARADC2_DATA()
esp.APB_SARADC.SetONETIME_SAMPLE_SARADC_ONETIME_START(0)
esp.APB_SARADC.SetONETIME_SAMPLE_SARADC2_ONETIME_SAMPLE(0)
esp.APB_SARADC.SetARB_CTRL_ADC_ARB_APB_FORCE(0)
esp.APB_SARADC.SetARB_CTRL_ADC_ARB_GRANT_FORCE(0)
}
return uint16(raw&0xfff) << 4
}
// adcSelfCalibration
const (
adcCalTimesC3 = 15
adcCalOffsetRangeC3 = uint32(4096)
adcCalRtcMagicC3 = uint32(0xADC1C401)
adcCalInitMinC3 = uint32(1000)
adcCalInitMaxC3 = uint32(4096)
adcGndOffsetCompC3 = uint32(0)
)
type adcSelfCalibration struct {
digiRefMv uint32
}
// calibrate 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{}
reg.sarEnable()
var adc1Code uint32
if saved, ok := c.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)
}
c.applyADC1Code(reg, adc1Code)
c.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() {
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)
}
// 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() {
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)
esp.APB_SARADC.SetARB_CTRL_ADC_ARB_GRANT_FORCE(1)
esp.APB_SARADC.SetONETIME_SAMPLE_SARADC2_ONETIME_SAMPLE(1)
}
// 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 {
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)
for esp.APB_SARADC.GetINT_RAW_APB_SARADC1_DONE_INT_RAW() == 0 {
}
raw := esp.APB_SARADC.GetSAR1DATA_STATUS_APB_SARADC1_DATA() & 0xfff
esp.APB_SARADC.SetONETIME_SAMPLE_SARADC_ONETIME_START(0)
return uint32(raw)
}
// readADC2 performs a single ADC2 conversion and returns the raw 12bit result (0..4095).
func (c *adcSelfCalibration) 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)
for esp.APB_SARADC.GetINT_RAW_APB_SARADC2_DONE_INT_RAW() == 0 {
}
raw := esp.APB_SARADC.GetSAR2DATA_STATUS_APB_SARADC2_DATA() & 0xfff
esp.APB_SARADC.SetONETIME_SAMPLE_SARADC_ONETIME_START(0)
esp.APB_SARADC.SetARB_CTRL_ADC_ARB_APB_FORCE(0)
esp.APB_SARADC.SetARB_CTRL_ADC_ARB_GRANT_FORCE(0)
return uint32(raw)
}
func (c *adcSelfCalibration) restoreFromRTC() (uint32, bool) {
if esp.RTC_CNTL.GetSTORE0() != adcCalRtcMagicC3 {
return 0, false
}
code := esp.RTC_CNTL.GetSTORE1()
if code < adcCalInitMinC3 || code > adcCalInitMaxC3 {
return 0, false
}
return code, true
}
func (c *adcSelfCalibration) saveToRTC(code uint32) {
if code < adcCalInitMinC3 || code > adcCalInitMaxC3 {
return
}
esp.RTC_CNTL.SetSTORE0(adcCalRtcMagicC3)
esp.RTC_CNTL.SetSTORE1(code)
}
// 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)
}
// 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)
}
+31 -5
View File
@@ -70,6 +70,7 @@ const (
PinInput
PinInputPullup
PinInputPulldown
PinAnalog
)
// Hardware pin numbers
@@ -121,6 +122,29 @@ const (
GPIO48 Pin = 48
)
const (
ADC0 Pin = GPIO1
ADC2 Pin = GPIO2
ADC3 Pin = GPIO3
ADC4 Pin = GPIO4
ADC5 Pin = GPIO5
ADC6 Pin = GPIO6
ADC7 Pin = GPIO7
ADC8 Pin = GPIO8
ADC9 Pin = GPIO9
ADC10 Pin = GPIO10
ADC11 Pin = GPIO11
ADC12 Pin = GPIO12
ADC13 Pin = GPIO13
ADC14 Pin = GPIO14
ADC15 Pin = GPIO15
ADC16 Pin = GPIO16
ADC17 Pin = GPIO17
ADC18 Pin = GPIO18
ADC19 Pin = GPIO19
ADC20 Pin = GPIO20
)
// Configure this pin with the given configuration.
func (p Pin) Configure(config PinConfig) {
// Output function 256 is a special value reserved for use as a regular GPIO
@@ -146,8 +170,10 @@ func (p Pin) configure(config PinConfig, signal uint32) {
// MCU_SEL: Function 1 is always GPIO
ioConfig |= (1 << esp.IO_MUX_GPIO_MCU_SEL_Pos)
// FUN_IE: Make this pin an input pin (always set for GPIO operation)
ioConfig |= esp.IO_MUX_GPIO_FUN_IE
// FUN_IE: disable for PinAnalog (high-Z for ADC), enable for digital
if config.Mode != PinAnalog {
ioConfig |= esp.IO_MUX_GPIO_FUN_IE
}
// DRV: Set drive strength to 20 mA as a default. Pins 17 and 18 are special
var drive uint32
@@ -158,7 +184,7 @@ func (p Pin) configure(config PinConfig, signal uint32) {
}
ioConfig |= (drive << esp.IO_MUX_GPIO_FUN_DRV_Pos)
// WPU/WPD: Select pull mode.
// WPU/WPD: no pulls for PinAnalog
if config.Mode == PinInputPullup {
ioConfig |= esp.IO_MUX_GPIO_FUN_WPU
} else if config.Mode == PinInputPulldown {
@@ -181,14 +207,14 @@ func (p Pin) configure(config PinConfig, signal uint32) {
// output signal, or the special value 256 which indicates regular GPIO
// usage.
p.outFunc().Set(signal)
case PinInput, PinInputPullup, PinInputPulldown:
case PinInput, PinInputPullup, PinInputPulldown, PinAnalog:
// Clear the 'output enable' bit.
if p < 32 {
esp.GPIO.ENABLE_W1TC.Set(1 << p)
} else {
esp.GPIO.ENABLE1_W1TC.Set(1 << (p - 32))
}
if signal != 256 {
if signal != 256 && config.Mode != PinAnalog {
// Signal is a peripheral function (not a simple GPIO). Connect this
// signal to the pin.
// Note that outFunc and inFunc work in the opposite direction.
+644
View File
@@ -0,0 +1,644 @@
//go:build esp32s3
// ESP32-S3: 2 SAR ADCs, 12-bit hardware; Get() returns 0..65520 (scaled from 12-bit).
// Pin mapping: ADC1 = GPIO 1..10 (channel = GPIO-1); ADC2 = GPIO 11..20 (channel = GPIO-11).
// Get() returns raw, uncalibrated ADC values; accurate 03.3V mapping should be done
// either by a two-point calibration in user code or by using the eFuse-based
// calibration logic (see IDF adc_cali / our ADCSelfCalibrate implementation).
//
// Registers used (TRM / IDF):
// SYSTEM: PERIP_RST_EN0.APB_SARADC_RST, PERIP_CLK_EN0.APB_SARADC_CLK_EN
// RTC_CNTL: ANA_CONF.SAR_I2C_PU, I2C_RESET_POR_FORCE_PU
// ADC1 RTC path (oneshot, TRM/IDF):
// SENS.SAR_MEAS1_MUX.SAR1_DIG_FORCE = 0 → ADC1 under RTC (not digital/APB)
// SENS.SAR_MEAS1_CTRL2.MEAS1_START_FORCE = 1, SAR1_EN_PAD_FORCE = 1 → SW triggers and selects channel
// Per conversion: set attenuation (SAR_ATTEN1), channel (SAR1_EN_PAD), then MEAS1_START_SAR 0→1; wait MEAS1_DONE_SAR; read MEAS1_DATA_SAR.
// SENS.SAR_MEAS1_CTRL1: amp/ref (FORCE_XPD_AMP etc). SAR_MEAS1_CTRL2: MEAS1_DONE_SAR (done), MEAS1_START_SAR (start), MEAS1_DATA_SAR (12-bit result).
// APB_SARADC: FSM_WAIT, CLKM, etc. used for clock/shared logic; ADC2 uses ARB_CTRL.
package machine
import (
"device/esp"
"errors"
"runtime/volatile"
"unsafe"
)
var adcDigiRefMv uint32
func InitADC() {
// SYSTEM: reset and enable APB_SARADC clock so SAR registers are accessible.
esp.SYSTEM.SetPERIP_RST_EN0_APB_SARADC_RST(1)
esp.SYSTEM.SetPERIP_CLK_EN0_APB_SARADC_CLK_EN(1)
esp.SYSTEM.SetPERIP_RST_EN0_APB_SARADC_RST(0)
// SENS.SAR_PERI_CLK_GATE_CONF: enable SENS SAR peripheral clock (matches Arduino/IDF runtime state).
esp.SENS.SetSAR_PERI_CLK_GATE_CONF_SARADC_CLK_EN(1)
// RTC_CNTL.ANA_CONF: keep internal SAR I2C (regI2C analog bus) powered and out of reset.
esp.RTC_CNTL.SetANA_CONF_I2C_RESET_POR_FORCE_PD(0)
esp.RTC_CNTL.SetANA_CONF_SAR_I2C_PU(1)
esp.RTC_CNTL.SetANA_CONF_I2C_RESET_POR_FORCE_PU(1)
// SENS.SAR_POWER: power up SAR analog block and enable SAR internal clock.
esp.SENS.SetSAR_POWER_XPD_SAR_FORCE_XPD_SAR(3)
esp.SENS.SetSAR_POWER_XPD_SAR_SARCLK_EN(1)
// SENS.SAR_MEAS1_CTRL1: force ADC1 front-end amplifier and reference on in RTC oneshot mode.
esp.SENS.SetSAR_MEAS1_CTRL1_FORCE_XPD_AMP(3)
esp.SENS.SetSAR_MEAS1_CTRL1_AMP_RST_FB_FORCE(3)
esp.SENS.SetSAR_MEAS1_CTRL1_AMP_SHORT_REF_FORCE(3)
esp.SENS.SetSAR_MEAS1_CTRL1_AMP_SHORT_REF_GND_FORCE(3)
// SENS.SAR_AMP_CTRL1/2: amplifier/reference settling timings (same as cold-boot defaults).
esp.SENS.SetSAR_AMP_CTRL1_SAR_AMP_WAIT1(10)
esp.SENS.SetSAR_AMP_CTRL1_SAR_AMP_WAIT2(10)
esp.SENS.SetSAR_AMP_CTRL2_SAR_XPD_SAR_AMP_FSM_IDLE(1)
esp.SENS.SetSAR_AMP_CTRL2_SAR_AMP_SHORT_REF_GND_FSM_IDLE(1)
// ADC2 uses the same InitADC() as ADC1 (shared APB_SARADC clock/FSM).
// SENS.SAR_MEAS2_CTRL1: ADC2 FSM wait timings for power-up/reset/standby.
esp.SENS.SetSAR_MEAS2_CTRL1_SAR_SAR2_XPD_WAIT(8)
esp.SENS.SetSAR_MEAS2_CTRL1_SAR_SAR2_RSTB_WAIT(8)
esp.SENS.SetSAR_MEAS2_CTRL1_SAR_SAR2_STANDBY_WAIT(100)
esp.SENS.SetSAR_MEAS2_CTRL1_SAR_SAR2_RSTB_FORCE(3)
// SENS.SAR_MEAS1_MUX / SAR_MEAS1_CTRL2: route ADC1 to RTC controller and use SW to select channel/start.
esp.SENS.SetSAR_MEAS1_MUX_SAR1_DIG_FORCE(0) // 0 = controlled by RTC/SENS, not digital/APB.
esp.SENS.SetSAR_MEAS1_CTRL2_MEAS1_START_FORCE(1) // SW triggers conversion.
esp.SENS.SetSAR_MEAS1_CTRL2_SAR1_EN_PAD_FORCE(1) // SW selects which ADC1 pad is enabled.
// APB_SARADC: shared FSM/clock config used by both ADC units and the ADC2 arbiter.
esp.APB_SARADC.SetFSM_WAIT_SARADC_XPD_WAIT(8)
esp.APB_SARADC.SetFSM_WAIT_SARADC_RSTB_WAIT(8)
esp.APB_SARADC.SetFSM_WAIT_SARADC_STANDBY_WAIT(100)
esp.APB_SARADC.SetCTRL_SARADC_XPD_SAR_FORCE(3)
esp.APB_SARADC.SetCTRL_SARADC_SAR_CLK_GATED(1)
esp.APB_SARADC.SetCTRL2_SARADC_SAR1_INV(0)
esp.APB_SARADC.SetCTRL2_SARADC_SAR2_INV(0)
esp.APB_SARADC.SetCLKM_CONF_CLK_SEL(2)
esp.APB_SARADC.SetCLKM_CONF_CLKM_DIV_NUM(1)
esp.APB_SARADC.SetCLKM_CONF_CLKM_DIV_B(0)
esp.APB_SARADC.SetCLKM_CONF_CLKM_DIV_A(0)
esp.APB_SARADC.SetCLKM_CONF_CLK_EN(1)
esp.APB_SARADC.SetFILTER_CTRL1_FILTER_FACTOR0(0)
esp.APB_SARADC.SetFILTER_CTRL1_FILTER_FACTOR1(0)
adcCal := adcCalibration{}
adcCal.calibrate()
adcDigiRefMv = adcCal.getDigiRef()
}
const (
attenDefault = 3 // 11 dB, ~0..3.3 V (IDF ADC_ATTEN_DB_12)
)
func setSensAtten1(ch, atten uint32) {
// SENS.SAR_ATTEN1: 2 bits per channel
v := esp.SENS.GetSAR_ATTEN1()
v &^= 3 << (ch * 2)
v |= (atten & 3) << (ch * 2)
esp.SENS.SetSAR_ATTEN1(v)
}
func setSensAtten2(ch, atten uint32) {
// SENS.SAR_ATTEN2: 2 bits per channel
v := esp.SENS.GetSAR_ATTEN2()
v &^= 3 << (ch * 2)
v |= (atten & 3) << (ch * 2)
esp.SENS.SetSAR_ATTEN2(v)
}
func (a ADC) Configure(config ADCConfig) error {
if a.Pin < 1 || a.Pin > 20 {
return errors.New("invalid ADC pin for ESP32-S3")
}
a.Pin.Configure(PinConfig{Mode: PinAnalog})
InitADC()
return nil
}
func (a ADC) Get() uint16 {
if a.Pin < 1 || a.Pin > 20 {
return 0
}
var ch uint32
var raw uint32
if a.Pin <= 10 {
ch = uint32(a.Pin - 1) // GPIO1→ch0 … GPIO10→ch9
esp.SENS.SetSAR_MEAS1_MUX_SAR1_DIG_FORCE(0)
esp.SENS.SetSAR_MEAS1_CTRL2_MEAS1_START_FORCE(1)
esp.SENS.SetSAR_MEAS1_CTRL2_SAR1_EN_PAD_FORCE(1)
setSensAtten1(ch, attenDefault)
esp.SENS.SetSAR_MEAS1_CTRL2_SAR1_EN_PAD(1 << ch)
for esp.SENS.GetSAR_SLAVE_ADDR1_SAR_SARADC_MEAS_STATUS() != 0 {
}
esp.SENS.SetSAR_MEAS1_CTRL2_MEAS1_START_SAR(0)
esp.SENS.SetSAR_MEAS1_CTRL2_MEAS1_START_SAR(1)
for esp.SENS.GetSAR_MEAS1_CTRL2_MEAS1_DONE_SAR() == 0 {
}
raw = esp.SENS.GetSAR_MEAS1_CTRL2_MEAS1_DATA_SAR()
} else {
ch = uint32(a.Pin - 11) // GPIO11→ch0 … GPIO20→ch9
// SENS.SAR_MEAS2_CTRL2: force SW control, select channel
esp.SENS.SetSAR_MEAS2_CTRL2_MEAS2_START_FORCE(1)
esp.SENS.SetSAR_MEAS2_CTRL2_SAR2_EN_PAD_FORCE(1)
esp.SENS.SetSAR_MEAS2_CTRL2_SAR2_EN_PAD(1 << ch)
setSensAtten2(ch, attenDefault)
// APB_SARADC.ARB_CTRL: grant ADC2 to APB for oneshot
esp.APB_SARADC.SetARB_CTRL_ADC_ARB_APB_FORCE(1)
esp.APB_SARADC.SetARB_CTRL_ADC_ARB_GRANT_FORCE(1)
// SENS.SAR_MEAS2_CTRL2.MEAS2_START_SAR: one-shot start
esp.SENS.SetSAR_MEAS2_CTRL2_MEAS2_START_SAR(0)
esp.SENS.SetSAR_MEAS2_CTRL2_MEAS2_START_SAR(1)
for esp.SENS.GetSAR_MEAS2_CTRL2_MEAS2_DONE_SAR() == 0 {
}
raw = esp.SENS.GetSAR_MEAS2_CTRL2_MEAS2_DATA_SAR()
esp.APB_SARADC.SetARB_CTRL_ADC_ARB_APB_FORCE(0)
esp.APB_SARADC.SetARB_CTRL_ADC_ARB_GRANT_FORCE(0)
}
return uint16(raw&0xfff) << 4
}
func (a ADC) GetVoltage() (raw uint32, v float64) {
const samples = 4
var sum uint32
for i := 0; i < samples; i++ {
sum += uint32(a.Get())
}
raw = sum / samples
// Default full-scale for 11 dB is approximately 3.3 V assuming
// Vref ≈ 1.1 V and gain ≈ 3. If eFuse provided a per-chip DIGI_REF
// (Vref in mV) via adcCalibration, use it to adjust the
// full-scale range instead.
scale := 3.3
if adcDigiRefMv != 0 {
scale = 3.0 * float64(adcDigiRefMv) / 1000.0
}
v = float64(raw) / 65520.0 * scale
return raw, v
}
// ADC hardware self-calibration for ESP32-S3.
//
// 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);
// 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()
// - 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).
// - Loop: 10 iterations, code 0..4096, binary search on self_cal==0; drop min/max;
// rounding (remainder%8 < 4 without +1, otherwise +1) — same as in adc_hal_common.c.
// - raw_check_valid: for ADC1 in IDF always true — we do not check it.
//
// Differences:
// - regI2C: not ROM helper but direct access to 0x6000E000 (protocol like I2C_RTC_CONFIG2).
// - cal_setup: same SENS/atten/controller fields, but through our registers.
// - Result is stored only in hardware for the current session (not in eFuse).
// - eFuse V1: init_code and digi_ref are taken from eFuse — same idea as Arduino/IDF.
const (
adcCalTimes = 10
adcCalOffsetMax = uint32(4096)
adcCalRtcMagic = uint32(0xADC1C401)
adcCalInitMin = uint32(2000)
adcCalInitMax = uint32(3900)
adcDigiRefMinMv = uint32(920)
adcDigiRefMaxMv = uint32(1150)
)
// 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{}
f := fuse{}
if vref, ok := f.adc1DigiRefAtten3(); ok {
c.digiRefMv = vref
}
if saved, ok := c.restoreFromRTC(); ok {
reg.sarEnable()
reg.adc1CalibrationInit(0)
c.adc1CalibrateHigh(reg, saved)
return
}
initCode, useEfuse := f.adc1InitCodeAtten3()
c.adc1CalibrationSetup(reg)
if useEfuse {
c.saveToRTC(initCode)
c.adc1CalibrateHigh(reg, initCode)
return
}
finalCode := c.adc1CalibrateLow(reg)
c.saveToRTC(finalCode)
c.adc1CalibrateHigh(reg, finalCode)
}
func (c *adcCalibration) getDigiRef() uint32 {
return c.digiRefMv
}
func (c *adcCalibration) adc1CalibrationSetup(reg regI2C) {
reg.sarEnable()
esp.SENS.SetSAR_MEAS1_MUX_SAR1_DIG_FORCE(0)
esp.SENS.SetSAR_MEAS1_CTRL2_MEAS1_START_FORCE(0)
esp.SENS.SetSAR_MEAS2_CTRL2_MEAS2_START_FORCE(0)
esp.SENS.SetSAR_MEAS1_CTRL2_SAR1_EN_PAD(0)
setSensAtten1(0, attenDefault)
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)
}
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 (c *adcCalibration) adc1CalibrateHigh(reg regI2C, code uint32) {
reg.adc1SetCalibrationParam(0, code)
reg.adc1CalibrationFinish(0)
c.adc1StartWithPadForce()
}
func (c *adcCalibration) adc1StartWithPadForce() {
esp.SENS.SetSAR_MEAS1_CTRL2_SAR1_EN_PAD_FORCE(1)
esp.SENS.SetSAR_MEAS1_CTRL2_MEAS1_START_FORCE(1)
}
// 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 {
for esp.SENS.GetSAR_SLAVE_ADDR1_SAR_SARADC_MEAS_STATUS() != 0 {
}
esp.SENS.SetSAR_MEAS1_CTRL2_MEAS1_START_SAR(0)
esp.SENS.SetSAR_MEAS1_CTRL2_MEAS1_START_SAR(1)
for esp.SENS.GetSAR_MEAS1_CTRL2_MEAS1_DONE_SAR() == 0 {
}
return uint32(esp.SENS.GetSAR_MEAS1_CTRL2_MEAS1_DATA_SAR() & 0xfff)
}
func (c *adcCalibration) restoreFromRTC() (uint32, bool) {
if esp.RTC_CNTL.GetSTORE0() != adcCalRtcMagic {
return 0, false
}
code := esp.RTC_CNTL.GetSTORE1()
if code < adcCalInitMin || code > adcCalInitMax {
return 0, false
}
return code, true
}
func (c *adcCalibration) 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).
efuseBase = uintptr(0x60007000)
// EFUSE_*_REG offsets mirror ESP-IDF's efuse_reg.h layout.
efuseClkReg = efuseBase + 0x1c8
efuseConfReg = efuseBase + 0x1cc
efuseCmdReg = efuseBase + 0x1d4
efuseDacConfReg = efuseBase + 0x1e8
efuseWrTimConf1Reg = efuseBase + 0x1f4
efuseWrTimConf2Reg = efuseBase + 0x1f8
efuseRdData4Reg = efuseBase + 0x6c // EFUSE_RD_WR_DIS_REG / RD_DATA4
efuseRdData5Reg = efuseBase + 0x70 // EFUSE_RD_REPEAT_DATA1_REG / RD_DATA5
efuseRdData7Reg = efuseBase + 0x78 // EFUSE_RD_REPEAT_DATA3_REG / RD_DATA7
// Read opcode and clock enable bit used by EFUSE HAL (see efuse_ll).
efuseReadOpCode = uint32(0x5AA5)
efuseClkEnBit = uint32(1 << 16)
efuseBlkVersionV1 = 1 // EFUSE_BLK_VERSION major version = 1
// SYSTEM_PERIP_CLK_EN0 register and EFUSE clock gate bit.
systemPeripClkEn0 = uintptr(0x600C0018)
systemEfuseClkEnBit = uint32(1 << 14)
)
type fuse struct{}
// adc1InitCodeAtten3 extracts the ADC1 INIT_CODE (offset trim) for
// attenuation index 3 (typically 11 dB) from EFUSE_BLK2. This mirrors
// the logic used by ESP-IDF's ADC calibration HAL for ESP32-S3.
//
// The code is built from four differential eFuse fields (diff0..diff3)
// and constant offsets (1850, 90, 70) as described in Espressif's
// internal calibration formulas.
func (f *fuse) adc1InitCodeAtten3() (uint32, bool) {
for try := 0; try < 2; try++ {
f.triggerReadSequence()
data4, data5, blkVer := f.readBlock2Data4Data5()
if blkVer != efuseBlkVersionV1 {
continue
}
diff0 := (data4 >> 21) & 0xFF
diff1 := (data4 >> 29) | ((data5 & 7) << 3)
diff2 := (data5 >> 3) & 0x3F
diff3 := (data5 >> 9) & 0x3F
icode0 := diff0 + 1850
icode1 := diff1 + icode0 + 90
icode2 := diff2 + icode1
icode3 := diff3 + icode2 + 70
if icode3 >= adcCalInitMin && icode3 <= adcCalInitMax {
return icode3, true
}
}
return 0, false
}
// adc1DigiRefAtten3 reads the digital reference (DIGI_REF) for
// ADC1 at attenuation index 3 from EFUSE_BLK2 / RD_DATA7. This is
// similar to what the ESP-IDF ADC calibration HAL uses when present.
func (f *fuse) adc1DigiRefAtten3() (uint32, bool) {
f.triggerReadSequence()
_, _, blkVer := f.readBlock2Data4Data5()
if blkVer != efuseBlkVersionV1 {
return 0, false
}
data7 := f.readBlock2Data7()
diff3 := (data7 >> 1) & 0xFF
digiRef := diff3 + 900
if digiRef < adcDigiRefMinMv || digiRef > adcDigiRefMaxMv {
return 0, false
}
return digiRef, true
}
// triggerReadSequence performs one eFuse read operation using the
// controller's timing/opcode sequence. This roughly corresponds to
// the low-level logic in the ESP-IDF eFuse HAL (see efuse_ll_* in
// the IDF sources and the "eFuse Manager" docs:
// https://docs.espressif.com/projects/esp-idf/en/latest/esp32s3/api-reference/system/efuse.html).
func (f *fuse) triggerReadSequence() {
clk := (*volatile.Register32)(unsafe.Pointer(systemPeripClkEn0))
clk.Set(clk.Get() | systemEfuseClkEnBit)
efuseClk := (*volatile.Register32)(unsafe.Pointer(efuseClkReg))
efuseClk.Set(efuseClk.Get() | efuseClkEnBit)
dac := (*volatile.Register32)(unsafe.Pointer(efuseDacConfReg))
dac.Set(0x28 | (0xFF << 9))
(*volatile.Register32)(unsafe.Pointer(efuseWrTimConf1Reg)).Set(0x3000 << 8)
(*volatile.Register32)(unsafe.Pointer(efuseWrTimConf2Reg)).Set(0x190)
(*volatile.Register32)(unsafe.Pointer(efuseConfReg)).Set(efuseReadOpCode)
cmd := (*volatile.Register32)(unsafe.Pointer(efuseCmdReg))
cmd.Set(1)
for cmd.Get()&1 != 0 {
}
}
// readBlock2Data4Data5 reads the EFUSE_BLK2 data words that contain
// ADC calibration and version information. It returns RD_DATA4,
// RD_DATA5 and the decoded block version (BLK_VERSION).
//
// Layout is derived from the ESP32-S3 TRM and IDF eFuse tables.
func (f *fuse) readBlock2Data4Data5() (data4, data5 uint32, blkVer uint8) {
data4 = (*volatile.Register32)(unsafe.Pointer(efuseRdData4Reg)).Get()
data5 = (*volatile.Register32)(unsafe.Pointer(efuseRdData5Reg)).Get()
blkVer = uint8(data4 & 3)
return data4, data5, blkVer
}
// readBlock2Data7 reads RD_DATA7 from EFUSE_BLK2, which for ADC
// calibration contains additional reference (DIGI_REF) data fields.
func (f *fuse) readBlock2Data7() uint32 {
return (*volatile.Register32)(unsafe.Pointer(efuseRdData7Reg)).Get()
}
// readAdcCalibBlock2 triggers an eFuse read and returns the raw
// EFUSE_BLK2 words used for ADC calibration (RD_DATA4/5) along
// with the decoded block version. This is a small helper similar
// in spirit to the internal IDF helpers around EFUSE_BLK2.
func (f *fuse) readAdcCalibBlock2() (data4, data5 uint32, blkVer uint8) {
f.triggerReadSequence()
return f.readBlock2Data4Data5()
}