From 94c69d2e8916a20403266dba334b4c1dd7672324 Mon Sep 17 00:00:00 2001 From: m-anti <68227205+m-anti@users.noreply.github.com> Date: Thu, 10 Sep 2026 14:25:47 +0200 Subject: [PATCH] machine/esp32c6: fix for ADC hang, initialises the APB_SARADC and Modem/ADC peripheral (#5647) --- src/machine/machine_esp32c6_adc.go | 158 ++++++++++++++++++++++++++--- 1 file changed, 144 insertions(+), 14 deletions(-) diff --git a/src/machine/machine_esp32c6_adc.go b/src/machine/machine_esp32c6_adc.go index 295144d67f..945d86bcc1 100644 --- a/src/machine/machine_esp32c6_adc.go +++ b/src/machine/machine_esp32c6_adc.go @@ -21,27 +21,41 @@ const ( atten11dB = 3 ) -// InitADC initialises the APB_SARADC peripheral on ESP32-C6. +// InitADC initialises the APB_SARADC and Modem/ADC peripheral on ESP32-C6. // On C6 the clock/reset gating moved to PCR (not SYSTEM as on C3), and the // SARADC CLKM divider configuration also lives in PCR. func InitADC() { + esp.PCR.SetSARADC_CONF_SARADC_CLK_EN(1) + // Reset and enable the SARADC bus clock via PCR. + esp.PCR.SetSARADC_CONF_SARADC_REG_CLK_EN(1) esp.PCR.SetSARADC_CONF_SARADC_RST_EN(1) - esp.PCR.SetSARADC_CONF_SARADC_CLK_EN(1) esp.PCR.SetSARADC_CONF_SARADC_RST_EN(0) - - // Select clock source 2 (PLL_F80M), divider = 1, no fractional. - esp.PCR.SetSARADC_CLKM_CONF_SARADC_CLKM_SEL(2) - esp.PCR.SetSARADC_CLKM_CONF_SARADC_CLKM_DIV_NUM(1) - esp.PCR.SetSARADC_CLKM_CONF_SARADC_CLKM_DIV_B(0) + esp.PCR.SetSARADC_CONF_SARADC_REG_RST_EN(1) + esp.PCR.SetSARADC_CONF_SARADC_REG_RST_EN(0) + + modemClockModuleEnableForADC() + + // Enable REG_I2C: Enter regi2c reset mode + esp.PMU.SetRF_PWC_PERIF_I2C_RSTB(0) + // Enable REGI2C for SAR_ADC and TSENS + esp.PMU.SetRF_PWC_XPD_PERIF_I2C(1) + // Release regi2c reset mode, enter work mode + esp.PMU.SetRF_PWC_PERIF_I2C_RSTB(1) + + // Select clock source 1 (PLL_F80M) + esp.PCR.SetSARADC_CLKM_CONF_SARADC_CLKM_SEL(1) + // At boot we have the CLKM values (4, 0, 0) but in ADC one-shot mode + // the CLKM values are changed to (15, 1, 0) (source: esp-idf hal/adc_oneshot_hal.c) + esp.PCR.SetSARADC_CLKM_CONF_SARADC_CLKM_DIV_NUM(15) // see ADC_LL_CLKM_DIV_NUM_DEFAULT hal/esp32c6/include/hal/adc_ll.h + esp.PCR.SetSARADC_CLKM_CONF_SARADC_CLKM_DIV_B(1) esp.PCR.SetSARADC_CLKM_CONF_SARADC_CLKM_DIV_A(0) esp.PCR.SetSARADC_CLKM_CONF_SARADC_CLKM_EN(1) - // Power up the SAR ADC and configure FSM timing (same register layout as C3). - 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) + // Power up the SAR ADC in software mode (3), no FSM config needed, FSM_REG defaults to 0x00FF0808 + esp.APB_SARADC.SetCTRL_SARADC_XPD_SAR_FORCE(3) + esp.APB_SARADC.SetCTRL_SARADC_SAR_CLK_GATED(1) + esp.APB_SARADC.SetCTRL_SARADC_SAR_CLK_DIV(1) adcSelfCalibrate() } @@ -62,17 +76,31 @@ func (a ADC) Get() uint16 { if a.Pin > 6 { return 0 } - esp.APB_SARADC.SetONETIME_SAMPLE_SARADC_ONETIME_ATTEN(atten11dB) + + // Clear stale DONE esp.APB_SARADC.SetINT_CLR_APB_SARADC1_DONE_INT_CLR(1) - esp.APB_SARADC.SetONETIME_SAMPLE_SARADC_ONETIME_START(0) + + // Disable oneshot conversion trigger for ADC unit + esp.APB_SARADC.SetONETIME_SAMPLE_SARADC1_ONETIME_SAMPLE(0) + esp.APB_SARADC.SetONETIME_SAMPLE_SARADC_ONETIME_CHANNEL(uint32(a.Pin)) + esp.APB_SARADC.SetONETIME_SAMPLE_SARADC_ONETIME_ATTEN(atten11dB) + + // Select ADC1 one-time mode esp.APB_SARADC.SetONETIME_SAMPLE_SARADC1_ONETIME_SAMPLE(1) + + // Trigger one-time conversion + esp.APB_SARADC.SetONETIME_SAMPLE_SARADC_ONETIME_START(0) + // No delay needed here because adc_ctrl_clk is fast (>= APB_CLK_FREQ/8). + // ESP-IDF evaluates this condition and calls esp_rom_delay_us(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() esp.APB_SARADC.SetONETIME_SAMPLE_SARADC_ONETIME_START(0) esp.APB_SARADC.SetONETIME_SAMPLE_SARADC1_ONETIME_SAMPLE(0) + return uint16(raw&0xfff) << 4 } @@ -390,3 +418,105 @@ func c6ApplyADC1Code(reg regI2C, code uint32) { reg.setCalibrationParam(0, code) reg.calibrationFinish(0) } + +// Enables the modem FE clock for the ADC. +// Ported from s_sar_power_acquire() in esp_hw_support/port/esp32c6/sar_periph_ctrl.c. +func modemClockModuleEnableForADC() { + initModemClocks() + + // Enable Modem Clk - ADC ( => see modem_clock_device_enable(ctx, 1)) + esp.MODEM_SYSCON.SetCLK_CONF1_CLK_FE_APB_EN(1) + esp.MODEM_SYSCON.SetCLK_CONF1_CLK_FE_80M_EN(1) +} + +type c6ModemClockDomain int + +const ( + modemClockDomainModemAPB c6ModemClockDomain = iota // see hal/include/hal/modem_clock_types.h + modemClockDomainModemPeriph + modemClockDomainWiFi + modemClockDomainBT + modemClockDomainModemFE + modemClockDomainIEEE802154 + modemClockDomainLPAPB + modemClockDomainI2CMaster + modemClockDomainCoex + modemClockDomainWiFiPwr + modemClockDomainMax +) + +const ( + pmuHpIcgModemCodeSleep = 0 // see esp_hw_support/include/esp_private/esp_pmu.h + pmuHpIcgModemCodeModem = 1 + pmuHpIcgModemCodeActive = 2 +) + +// The ICG code's bit 0, 1 and 2 indicates the ICG state +// of pmu SLEEP, MODEM and ACTIVE mode respectively +const ( + icgNogatingActive = 1 << pmuHpIcgModemCodeActive // see esp_hw_support/modem_clock.c + icgNogatingSleep = 1 << pmuHpIcgModemCodeSleep + icgNogatingModem = 1 << pmuHpIcgModemCodeModem +) + +// Sets the default modem ICG bitmaps. +// Ported from modem_clock_module_icg_map_init_all() in esp_hw_support/modem_clock.c. +func initModemClocks() { + for domain := modemClockDomainModemAPB; domain < modemClockDomainMax; domain++ { + bitmap := getInitialGatingMode(domain) + modemClockSetClockDomainICGBitmap(domain, bitmap) + } +} + +// Ported from initial_gating_mode in esp_hw_support/modem_clock.c. +func getInitialGatingMode(domain c6ModemClockDomain) uint32 { + switch domain { + case modemClockDomainModemAPB: + return esp.MODEM_SYSCON.GetCLK_CONF_POWER_ST_CLK_MODEM_APB_ST_MAP() | icgNogatingActive | icgNogatingModem + case modemClockDomainModemPeriph: + return esp.MODEM_SYSCON.GetCLK_CONF_POWER_ST_CLK_MODEM_PERI_ST_MAP() | icgNogatingActive + case modemClockDomainWiFi: + return esp.MODEM_SYSCON.GetCLK_CONF_POWER_ST_CLK_WIFI_ST_MAP() | icgNogatingActive | icgNogatingModem + case modemClockDomainBT: + return esp.MODEM_SYSCON.GetCLK_CONF_POWER_ST_CLK_BT_ST_MAP() | icgNogatingActive + case modemClockDomainModemFE: + return esp.MODEM_SYSCON.GetCLK_CONF_POWER_ST_CLK_FE_ST_MAP() | icgNogatingActive | icgNogatingModem + case modemClockDomainIEEE802154: + return esp.MODEM_SYSCON.GetCLK_CONF_POWER_ST_CLK_ZB_ST_MAP() | icgNogatingActive + case modemClockDomainLPAPB: + return esp.MODEM_LPCON.GetCLK_CONF_POWER_ST_CLK_LP_APB_ST_MAP() | icgNogatingActive | icgNogatingModem + case modemClockDomainI2CMaster: + return esp.MODEM_LPCON.GetCLK_CONF_POWER_ST_CLK_I2C_MST_ST_MAP() | icgNogatingActive | icgNogatingModem + case modemClockDomainCoex: + return esp.MODEM_LPCON.GetCLK_CONF_POWER_ST_CLK_COEX_ST_MAP() | icgNogatingActive | icgNogatingModem + case modemClockDomainWiFiPwr: + return esp.MODEM_LPCON.GetCLK_CONF_POWER_ST_CLK_WIFIPWR_ST_MAP() | icgNogatingActive | icgNogatingModem + } + return 0 +} + +// Ported from hal/esp32c6/modem_clock_hal.c. +func modemClockSetClockDomainICGBitmap(domain c6ModemClockDomain, bitmap uint32) { + switch domain { + case modemClockDomainModemAPB: + esp.MODEM_SYSCON.SetCLK_CONF_POWER_ST_CLK_MODEM_APB_ST_MAP(bitmap) + case modemClockDomainModemPeriph: + esp.MODEM_SYSCON.SetCLK_CONF_POWER_ST_CLK_MODEM_PERI_ST_MAP(bitmap) + case modemClockDomainWiFi: + esp.MODEM_SYSCON.SetCLK_CONF_POWER_ST_CLK_WIFI_ST_MAP(bitmap) + case modemClockDomainBT: + esp.MODEM_SYSCON.SetCLK_CONF_POWER_ST_CLK_BT_ST_MAP(bitmap) + case modemClockDomainModemFE: + esp.MODEM_SYSCON.SetCLK_CONF_POWER_ST_CLK_FE_ST_MAP(bitmap) + case modemClockDomainIEEE802154: + esp.MODEM_SYSCON.SetCLK_CONF_POWER_ST_CLK_ZB_ST_MAP(bitmap) + case modemClockDomainLPAPB: + esp.MODEM_LPCON.SetCLK_CONF_POWER_ST_CLK_LP_APB_ST_MAP(bitmap) + case modemClockDomainI2CMaster: + esp.MODEM_LPCON.SetCLK_CONF_POWER_ST_CLK_I2C_MST_ST_MAP(bitmap) + case modemClockDomainCoex: + esp.MODEM_LPCON.SetCLK_CONF_POWER_ST_CLK_COEX_ST_MAP(bitmap) + case modemClockDomainWiFiPwr: + esp.MODEM_LPCON.SetCLK_CONF_POWER_ST_CLK_WIFIPWR_ST_MAP(bitmap) + } +}