Skip to content

Commit df710c8

Browse files
committed
machine/esp32c6: add ADC driver
Adds machine_esp32c6_adc.go implementing the machine.ADC interface for ESP32-C6 (ADC1 only, GPIO0–GPIO6, channels 0–6; there is no ADC2). Signed-off-by: deadprogram <ron@hybridgroup.com>
1 parent 10224a4 commit df710c8

1 file changed

Lines changed: 392 additions & 0 deletions

File tree

src/machine/machine_esp32c6_adc.go

Lines changed: 392 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,392 @@
1+
//go:build esp32c6
2+
3+
package machine
4+
5+
import (
6+
"device/esp"
7+
"errors"
8+
"runtime/volatile"
9+
"unsafe"
10+
)
11+
12+
// newRegI2C returns the regI2C configured for ESP32-C6: hostID=0, drefInit=1.
13+
// I2C_SAR_ADC_HOSTID = 0 per soc/esp32c6/include/soc/regi2c_saradc.h.
14+
func newRegI2C() regI2C { return regI2C{hostID: 0, drefInit: 1} }
15+
16+
const (
17+
// ADC attenuation values for ESP32-C6 APB_SARADC.
18+
// 0 dB : ~0 .. 1.1 V
19+
// 11 dB : ~0 .. 3.3 V (matches typical VDD)
20+
atten0dB = 0
21+
atten11dB = 3
22+
)
23+
24+
// InitADC initialises the APB_SARADC peripheral on ESP32-C6.
25+
// On C6 the clock/reset gating moved to PCR (not SYSTEM as on C3), and the
26+
// SARADC CLKM divider configuration also lives in PCR.
27+
func InitADC() {
28+
// Reset and enable the SARADC bus clock via PCR.
29+
esp.PCR.SetSARADC_CONF_SARADC_RST_EN(1)
30+
esp.PCR.SetSARADC_CONF_SARADC_CLK_EN(1)
31+
esp.PCR.SetSARADC_CONF_SARADC_RST_EN(0)
32+
33+
// Select clock source 2 (PLL_F80M), divider = 1, no fractional.
34+
esp.PCR.SetSARADC_CLKM_CONF_SARADC_CLKM_SEL(2)
35+
esp.PCR.SetSARADC_CLKM_CONF_SARADC_CLKM_DIV_NUM(1)
36+
esp.PCR.SetSARADC_CLKM_CONF_SARADC_CLKM_DIV_B(0)
37+
esp.PCR.SetSARADC_CLKM_CONF_SARADC_CLKM_DIV_A(0)
38+
esp.PCR.SetSARADC_CLKM_CONF_SARADC_CLKM_EN(1)
39+
40+
// Power up the SAR ADC and configure FSM timing (same register layout as C3).
41+
esp.APB_SARADC.SetCTRL_SARADC_XPD_SAR_FORCE(1)
42+
esp.APB_SARADC.SetFSM_WAIT_SARADC_XPD_WAIT(8)
43+
esp.APB_SARADC.SetFSM_WAIT_SARADC_RSTB_WAIT(8)
44+
esp.APB_SARADC.SetFSM_WAIT_SARADC_STANDBY_WAIT(100)
45+
46+
adcSelfCalibrate()
47+
}
48+
49+
// ESP32-C6 ADC pin mapping: ADC1 = GPIO0–GPIO6 (ch 0–6). There is no ADC2.
50+
// (The machine_esp32c6.go file defines ADC0..ADC6 as GPIO0..GPIO6.)
51+
func (a ADC) Configure(config ADCConfig) error {
52+
if a.Pin > 6 {
53+
return errors.New("invalid ADC pin for ESP32-C6")
54+
}
55+
a.Pin.Configure(PinConfig{Mode: PinAnalog})
56+
return nil
57+
}
58+
59+
// Get performs a single ADC1 conversion and returns a 16-bit value.
60+
// The raw 12-bit result (0..4095) is left-shifted by 4 to fill 16 bits.
61+
func (a ADC) Get() uint16 {
62+
if a.Pin > 6 {
63+
return 0
64+
}
65+
esp.APB_SARADC.SetONETIME_SAMPLE_SARADC_ONETIME_ATTEN(atten11dB)
66+
esp.APB_SARADC.SetINT_CLR_APB_SARADC1_DONE_INT_CLR(1)
67+
esp.APB_SARADC.SetONETIME_SAMPLE_SARADC_ONETIME_START(0)
68+
esp.APB_SARADC.SetONETIME_SAMPLE_SARADC_ONETIME_CHANNEL(uint32(a.Pin))
69+
esp.APB_SARADC.SetONETIME_SAMPLE_SARADC1_ONETIME_SAMPLE(1)
70+
esp.APB_SARADC.SetONETIME_SAMPLE_SARADC_ONETIME_START(1)
71+
for esp.APB_SARADC.GetINT_RAW_APB_SARADC1_DONE_INT_RAW() == 0 {
72+
}
73+
raw := esp.APB_SARADC.GetSAR1DATA_STATUS_APB_SARADC1_DATA()
74+
esp.APB_SARADC.SetONETIME_SAMPLE_SARADC_ONETIME_START(0)
75+
esp.APB_SARADC.SetONETIME_SAMPLE_SARADC1_ONETIME_SAMPLE(0)
76+
return uint16(raw&0xfff) << 4
77+
}
78+
79+
// ── regI2C: internal I2C-bus (LP_I2C_ANA_MST) for SAR ADC calibration ───────
80+
//
81+
// On ESP32-C6 the "REGI2C" master moved from the embedded SENS/APB_SARADC
82+
// controller (0x6000_E000) used on C3/S3 to the dedicated LP_I2C_ANA_MST
83+
// peripheral at 0x600b_2400. The SAR ADC block address and register layout
84+
// (DREF, ENCAL_GND, INIT_CODE) remain identical to C3.
85+
//
86+
// LP_I2C_ANA_MST.I2C0_CTRL bit layout (25-bit command field):
87+
// [7:0] = slave block address (0x69 for I2C_SAR_ADC)
88+
// [15:8] = register address within the block
89+
// [23:16]= write data (8 bits)
90+
// [24] = WR_CNTL: 0=read, 1=write
91+
// [25] = BUSY (read-only, set by hardware while processing)
92+
//
93+
// Source: components/esp_rom/patches/esp_rom_regi2c_esp32c6.c in esp-idf
94+
95+
// regI2C wraps the internal I2C bus used for SAR ADC calibration registers.
96+
// Fields hold chip-specific parameters.
97+
type regI2C struct {
98+
// hostID is the I2C_SAR_ADC_HOSTID (0 for ESP32-C6, matching regi2c_saradc.h).
99+
hostID uint8
100+
// drefInit is the DREF reference value written during calibrationInit (1 for C6).
101+
drefInit uint8
102+
}
103+
104+
// SAR ADC I2C register layout — identical to ESP32-C3 / ESP32-S3.
105+
// Source: soc/esp32c6/include/soc/regi2c_saradc.h
106+
const (
107+
i2cSarADC = uint8(0x69)
108+
109+
adc1DrefAddr = uint8(0x2)
110+
adc1DrefMSB = uint8(6)
111+
adc1DrefLSB = uint8(4)
112+
adc2DrefAddr = uint8(0x5)
113+
adc2DrefMSB = uint8(6)
114+
adc2DrefLSB = uint8(4)
115+
116+
adc1EncalGndAddr = uint8(0x7)
117+
adc1EncalGndMSB = uint8(5)
118+
adc1EncalGndLSB = uint8(5)
119+
adc2EncalGndAddr = uint8(0x7)
120+
adc2EncalGndMSB = uint8(7)
121+
adc2EncalGndLSB = uint8(7)
122+
123+
adc1InitCodeHighAddr = uint8(0x1)
124+
adc1InitCodeHighMSB = uint8(3)
125+
adc1InitCodeHighLSB = uint8(0)
126+
adc1InitCodeLowAddr = uint8(0x0)
127+
adc1InitCodeLowMSB = uint8(7)
128+
adc1InitCodeLowLSB = uint8(0)
129+
adc2InitCodeHighAddr = uint8(0x4)
130+
adc2InitCodeHighMSB = uint8(3)
131+
adc2InitCodeHighLSB = uint8(0)
132+
adc2InitCodeLowAddr = uint8(0x3)
133+
adc2InitCodeLowMSB = uint8(7)
134+
adc2InitCodeLowLSB = uint8(0)
135+
136+
// adcCalOffsetRange is the binary search upper bound (12-bit full scale).
137+
adcCalOffsetRange = uint32(4096)
138+
// adcCalMaxIterations caps binary search iterations.
139+
adcCalMaxIterations = 16
140+
)
141+
142+
// LP_I2C_ANA_MST I2C0_CTRL bit-field shifts (see file header comment).
143+
const (
144+
c6SlaveIDShift = 0 // bits [7:0]
145+
c6AddrShift = 8 // bits [15:8]
146+
c6DataShift = 16 // bits [23:16]
147+
c6WrCntlShift = 24 // bit [24]
148+
c6BusyBit = uint32(1 << 25)
149+
150+
// c6SarI2CDeviceEn is BIT(7) in LP_I2C_ANA_MST.DEVICE_EN for I2C_SAR_ADC (0x69).
151+
c6SarI2CDeviceEn = uint32(1 << 7)
152+
)
153+
154+
// ANA_CONFIG / ANA_CONFIG2 register addresses and bits for the internal SAR I2C
155+
// domain on ESP32-C6. These differ from C3's SENS block (0x6000_E044/048).
156+
// Source: soc/esp32c6/include/soc/regi2c_defs.h
157+
const (
158+
c6AnaConfigReg = uintptr(0x600AF81C) // clear ANA_I2C_SAR_FORCE_PD (bit 18)
159+
c6AnaConfig2Reg = uintptr(0x600AF820) // set ANA_I2C_SAR_FORCE_PU (bit 16)
160+
c6SarForcePD = uint32(1 << 18)
161+
c6SarForcePU = uint32(1 << 16)
162+
)
163+
164+
// sarEnable powers up the internal SAR I2C domain and enables the LP_I2C_ANA_MST
165+
// clock and SAR slave device before any regI2C access.
166+
// Matches regi2c_ctrl_ll_i2c_saradc_enable() + regi2c_enable_block(REGI2C_SAR_I2C).
167+
func (r regI2C) sarEnable() {
168+
cfg := (*volatile.Register32)(unsafe.Pointer(c6AnaConfigReg))
169+
cfg2 := (*volatile.Register32)(unsafe.Pointer(c6AnaConfig2Reg))
170+
cfg.Set(cfg.Get() &^ c6SarForcePD)
171+
cfg2.Set(cfg2.Get() | c6SarForcePU)
172+
173+
// Enable the LP_I2C_ANA_MST master clock (MODEM_LPCON.CLK_CONF bit 2).
174+
esp.MODEM_LPCON.SetCLK_CONF_CLK_I2C_MST_EN(1)
175+
// Enable the master's own clock gate (LP_I2C_ANA_MST.DATE bit 28).
176+
esp.LP_I2C_ANA_MST.SetDATE_LP_I2C_ANA_MAST_I2C_MAT_CLK_EN(1)
177+
// Enable the SAR ADC slave device (DEVICE_EN bit 7).
178+
dev := esp.LP_I2C_ANA_MST.GetDEVICE_EN_LP_I2C_ANA_MAST_I2C_DEVICE_EN()
179+
esp.LP_I2C_ANA_MST.SetDEVICE_EN_LP_I2C_ANA_MAST_I2C_DEVICE_EN(dev | c6SarI2CDeviceEn)
180+
}
181+
182+
// writeMask implements the REGI2C_WRITE_MASK macro for ESP32-C6 via LP_I2C_ANA_MST.
183+
// It reads the current byte at regAddr, updates the [msb:lsb] bitfield, and writes
184+
// it back. Matches esp_rom_regi2c_write_mask() in esp_rom_regi2c_esp32c6.c.
185+
func (r regI2C) writeMask(regAddr, msb, lsb, data uint8) {
186+
ctrl := &esp.LP_I2C_ANA_MST.I2C0_CTRL
187+
rdata := &esp.LP_I2C_ANA_MST.I2C0_DATA
188+
189+
// Issue a read command: slave_id | (reg_addr << 8), no WR_CNTL bit.
190+
readCmd := (uint32(i2cSarADC) << c6SlaveIDShift) | (uint32(regAddr) << c6AddrShift)
191+
volatile.StoreUint32(&ctrl.Reg, readCmd)
192+
for volatile.LoadUint32(&ctrl.Reg)&c6BusyBit != 0 {
193+
}
194+
cur := volatile.LoadUint32(&rdata.Reg) & 0xFF
195+
196+
// Modify the [msb:lsb] bitfield.
197+
mask := uint32(1<<(msb-lsb+1)-1) << lsb
198+
cur &^= mask
199+
cur |= uint32(data&(1<<(msb-lsb+1)-1)) << lsb
200+
201+
// Issue a write command: slave_id | (reg_addr<<8) | WR_CNTL | (data<<16).
202+
writeCmd := (uint32(i2cSarADC) << c6SlaveIDShift) |
203+
(uint32(regAddr) << c6AddrShift) |
204+
(uint32(1) << c6WrCntlShift) |
205+
((cur & 0xFF) << c6DataShift)
206+
volatile.StoreUint32(&ctrl.Reg, writeCmd)
207+
for volatile.LoadUint32(&ctrl.Reg)&c6BusyBit != 0 {
208+
}
209+
}
210+
211+
// calibrationInit sets the DREF reference for the selected ADC unit.
212+
func (r regI2C) calibrationInit(adcN uint8) {
213+
if adcN == 0 {
214+
r.writeMask(adc1DrefAddr, adc1DrefMSB, adc1DrefLSB, r.drefInit)
215+
} else {
216+
r.writeMask(adc2DrefAddr, adc2DrefMSB, adc2DrefLSB, r.drefInit)
217+
}
218+
}
219+
220+
// calibrationPrepare enables ENCAL_GND so the ADC input is shorted to ground.
221+
func (r regI2C) calibrationPrepare(adcN uint8) {
222+
if adcN == 0 {
223+
r.writeMask(adc1EncalGndAddr, adc1EncalGndMSB, adc1EncalGndLSB, 1)
224+
} else {
225+
r.writeMask(adc2EncalGndAddr, adc2EncalGndMSB, adc2EncalGndLSB, 1)
226+
}
227+
}
228+
229+
// calibrationFinish clears ENCAL_GND to reconnect the ADC input to the pad.
230+
func (r regI2C) calibrationFinish(adcN uint8) {
231+
if adcN == 0 {
232+
r.writeMask(adc1EncalGndAddr, adc1EncalGndMSB, adc1EncalGndLSB, 0)
233+
} else {
234+
r.writeMask(adc2EncalGndAddr, adc2EncalGndMSB, adc2EncalGndLSB, 0)
235+
}
236+
}
237+
238+
// setCalibrationParam writes the INIT_CODE (offset trim) for the selected ADC unit.
239+
func (r regI2C) setCalibrationParam(adcN uint8, param uint32) {
240+
msb := uint8(param >> 8)
241+
lsb := uint8(param & 0xFF)
242+
if adcN == 0 {
243+
r.writeMask(adc1InitCodeHighAddr, adc1InitCodeHighMSB, adc1InitCodeHighLSB, msb)
244+
r.writeMask(adc1InitCodeLowAddr, adc1InitCodeLowMSB, adc1InitCodeLowLSB, lsb)
245+
} else {
246+
r.writeMask(adc2InitCodeHighAddr, adc2InitCodeHighMSB, adc2InitCodeHighLSB, msb)
247+
r.writeMask(adc2InitCodeLowAddr, adc2InitCodeLowMSB, adc2InitCodeLowLSB, lsb)
248+
}
249+
}
250+
251+
// calibrateBinarySearch runs the ADC self-calibration binary search loop.
252+
// It performs 'iterations' rounds, drops the min/max outliers, and returns
253+
// the rounded mean of the remaining values. Matches adc_hal_self_calibration().
254+
func (r regI2C) calibrateBinarySearch(adcN uint8, iterations int, readADC func() uint32) uint32 {
255+
if iterations > adcCalMaxIterations {
256+
iterations = adcCalMaxIterations
257+
}
258+
var codeList [adcCalMaxIterations]uint32
259+
var codeSum uint32
260+
261+
for rpt := 0; rpt < iterations; rpt++ {
262+
codeH := adcCalOffsetRange
263+
codeL := uint32(0)
264+
chkCode := (codeH + codeL) / 2
265+
r.setCalibrationParam(adcN, chkCode)
266+
selfCal := readADC()
267+
268+
for codeH-codeL > 1 {
269+
if selfCal == 0 {
270+
codeH = chkCode
271+
} else {
272+
codeL = chkCode
273+
}
274+
chkCode = (codeH + codeL) / 2
275+
r.setCalibrationParam(adcN, chkCode)
276+
selfCal = readADC()
277+
if codeH-codeL == 1 {
278+
chkCode++
279+
r.setCalibrationParam(adcN, chkCode)
280+
selfCal = readADC()
281+
}
282+
}
283+
codeList[rpt] = chkCode
284+
codeSum += chkCode
285+
}
286+
287+
codeMin := codeList[0]
288+
codeMax := codeList[0]
289+
for i := 0; i < iterations; i++ {
290+
if codeList[i] < codeMin {
291+
codeMin = codeList[i]
292+
}
293+
if codeList[i] > codeMax {
294+
codeMax = codeList[i]
295+
}
296+
}
297+
remaining := codeSum - codeMax - codeMin
298+
divisor := uint32(iterations - 2)
299+
finalCode := remaining / divisor
300+
if remaining%divisor >= 4 {
301+
finalCode++
302+
}
303+
return finalCode
304+
}
305+
306+
// ── Self-calibration ──────────────────────────────────────────────────────────
307+
308+
const (
309+
adcCalTimesC6 = 15
310+
adcCalRtcMagicC6 = uint32(0xADC1C601) // magic distinguishes C6 from C3
311+
adcCalInitMinC6 = uint32(1000)
312+
adcCalInitMaxC6 = uint32(4096)
313+
)
314+
315+
// adcSelfCalibrate runs a self-calibration for ADC1 (the only ADC unit on C6).
316+
// The calibration code is cached in LP_AON scratch registers to survive sleep.
317+
// eFuse calibration is not used: the fields are often unprogrammed.
318+
func adcSelfCalibrate() {
319+
reg := newRegI2C()
320+
reg.sarEnable()
321+
322+
var adc1Code uint32
323+
if saved, ok := c6RestoreFromLP(); ok {
324+
adc1Code = saved
325+
} else {
326+
c6CalSetupADC1()
327+
reg.calibrationInit(0)
328+
reg.calibrationPrepare(0)
329+
adc1Code = reg.calibrateBinarySearch(0, adcCalTimesC6, readADC1)
330+
if adc1Code < adcCalInitMinC6 {
331+
adc1Code = adcCalInitMinC6
332+
}
333+
if adc1Code > adcCalInitMaxC6 {
334+
adc1Code = adcCalInitMaxC6
335+
}
336+
c6SaveToLP(adc1Code)
337+
reg.calibrationFinish(0)
338+
}
339+
340+
c6ApplyADC1Code(reg, adc1Code)
341+
}
342+
343+
// c6CalSetupADC1 configures APB_SARADC for oneshot ADC1 ch0 with fixed attenuation.
344+
func c6CalSetupADC1() {
345+
esp.APB_SARADC.SetONETIME_SAMPLE_SARADC_ONETIME_ATTEN(atten11dB)
346+
esp.APB_SARADC.SetONETIME_SAMPLE_SARADC_ONETIME_CHANNEL(0)
347+
esp.APB_SARADC.SetONETIME_SAMPLE_SARADC1_ONETIME_SAMPLE(1)
348+
}
349+
350+
// readADC1 performs a single ADC1 conversion and returns the raw 12-bit result.
351+
func readADC1() uint32 {
352+
esp.APB_SARADC.SetINT_CLR_APB_SARADC1_DONE_INT_CLR(1)
353+
esp.APB_SARADC.SetONETIME_SAMPLE_SARADC_ONETIME_START(0)
354+
esp.APB_SARADC.SetONETIME_SAMPLE_SARADC_ONETIME_START(1)
355+
for esp.APB_SARADC.GetINT_RAW_APB_SARADC1_DONE_INT_RAW() == 0 {
356+
}
357+
raw := esp.APB_SARADC.GetSAR1DATA_STATUS_APB_SARADC1_DATA() & 0xfff
358+
esp.APB_SARADC.SetONETIME_SAMPLE_SARADC_ONETIME_START(0)
359+
return uint32(raw)
360+
}
361+
362+
// c6RestoreFromLP reads the saved calibration code from LP_AON scratch registers.
363+
// On C6, LP_AON replaces the C3's RTC_CNTL for scratch storage.
364+
func c6RestoreFromLP() (uint32, bool) {
365+
if esp.LP_AON.GetSTORE0() != adcCalRtcMagicC6 {
366+
return 0, false
367+
}
368+
code := esp.LP_AON.GetSTORE1()
369+
if code < adcCalInitMinC6 || code > adcCalInitMaxC6 {
370+
return 0, false
371+
}
372+
return code, true
373+
}
374+
375+
// c6SaveToLP stores the calibration code in LP_AON scratch registers.
376+
func c6SaveToLP(code uint32) {
377+
if code < adcCalInitMinC6 || code > adcCalInitMaxC6 {
378+
return
379+
}
380+
esp.LP_AON.SetSTORE0(adcCalRtcMagicC6)
381+
esp.LP_AON.SetSTORE1(code)
382+
}
383+
384+
// c6ApplyADC1Code sets ADC1 init code and finishes calibration.
385+
// ESP32-C6 has no ADC2 so only ADC1 (adcN=0) needs to be configured.
386+
func c6ApplyADC1Code(reg regI2C, code uint32) {
387+
c6CalSetupADC1()
388+
reg.calibrationInit(0)
389+
reg.calibrationPrepare(0)
390+
reg.setCalibrationParam(0, code)
391+
reg.calibrationFinish(0)
392+
}

0 commit comments

Comments
 (0)