mirror of
https://github.com/tinygo-org/tinygo.git
synced 2026-08-04 11:07:46 +00:00
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:
@@ -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
@@ -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)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
|
||||
@@ -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 = GPIO0–GPIO4 (ch 0–4), ADC2 = GPIO5 (ch 0). ADC2 shares with Wi‑Fi;
|
||||
// readings may be noisy when Wi‑Fi 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 self‑calibration.
|
||||
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 12‑bit 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 12‑bit 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 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<<i2cMstDataShift)&i2cMstDataMask)
|
||||
r.waitIdle(reg)
|
||||
}
|
||||
@@ -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.
|
||||
|
||||
@@ -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 0–3.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()
|
||||
}
|
||||
Reference in New Issue
Block a user