//go:build rp2040 package machine import ( "device/rp" "errors" "sync" ) // ADCChannel is the ADC peripheral mux channel. 0-4. type ADCChannel uint8 // ADC channels. Only ADC_TEMP_SENSOR is public. The other channels are accessed via Machine.ADC objects const ( adc0_CH ADCChannel = iota adc1_CH adc2_CH adc3_CH // Note: GPIO29 not broken out on pico board adcTempSensor // Internal temperature sensor channel ) // Used to serialise ADC sampling var adcLock sync.Mutex // ADC peripheral reference voltage (mV) var adcAref uint32 // InitADC resets the ADC peripheral. func InitADC() { rp.RESETS.RESET.SetBits(rp.RESETS_RESET_ADC) rp.RESETS.RESET.ClearBits(rp.RESETS_RESET_ADC) for !rp.RESETS.RESET_DONE.HasBits(rp.RESETS_RESET_ADC) { } // enable ADC rp.ADC.CS.Set(rp.ADC_CS_EN) adcAref = 3300 waitForReady() } // Configure sets the ADC pin to analog input mode. func (a ADC) Configure(config ADCConfig) error { c, err := a.GetADCChannel() if err != nil { return err } return c.Configure(config) } // Get returns a one-shot ADC sample reading. func (a ADC) Get() uint16 { if c, err := a.GetADCChannel(); err == nil { return c.getOnce() } // Not an ADC pin! return 0 } // GetADCChannel returns the channel associated with the ADC pin. func (a ADC) GetADCChannel() (c ADCChannel, err error) { err = nil switch a.Pin { case ADC0: c = adc0_CH case ADC1: c = adc1_CH case ADC2: c = adc2_CH case ADC3: c = adc3_CH default: err = errors.New("no ADC channel for pin value") } return c, err } // Configure sets the channel's associated pin to analog input mode. // The powered on temperature sensor increases ADC_AVDD current by approximately 40 μA. func (c ADCChannel) Configure(config ADCConfig) error { if config.Reference != 0 { adcAref = config.Reference } p, err := c.Pin() if err != nil { return err } p.Configure(PinConfig{Mode: PinAnalog}) return nil } // getOnce returns a one-shot ADC sample reading from an ADC channel. func (c ADCChannel) getOnce() uint16 { // Make it safe to sample multiple ADC channels in separate go routines. adcLock.Lock() rp.ADC.CS.ReplaceBits(uint32(c), 0b111, rp.ADC_CS_AINSEL_Pos) rp.ADC.CS.SetBits(rp.ADC_CS_START_ONCE) waitForReady() adcLock.Unlock() // rp2040 is a 12-bit ADC, scale raw reading to 16-bits. return uint16(rp.ADC.RESULT.Get()) << 4 } // getVoltage does a one-shot sample and returns a millivolts reading. // Integer portion is stored in the high 16 bits and fractional in the low 16 bits. func (c ADCChannel) getVoltage() uint32 { return (adcAref << 16) / (1 << 12) * uint32(c.getOnce()>>4) } // ReadTemperature does a one-shot sample of the internal temperature sensor and returns a milli-celsius reading. func ReadTemperature() (millicelsius int32) { if rp.ADC.CS.Get()&rp.ADC_CS_EN == 0 { InitADC() } // Enable temperature sensor bias source rp.ADC.CS.SetBits(rp.ADC_CS_TS_EN) // T = 27 - (ADC_voltage - 0.706)/0.001721 return (27000<<16 - (int32(adcTempSensor.getVoltage())-706<<16)*581) >> 16 } // waitForReady spins waiting for the ADC peripheral to become ready. func waitForReady() { for !rp.ADC.CS.HasBits(rp.ADC_CS_READY) { } } // The Pin method returns the GPIO Pin associated with the ADC mux channel, if it has one. func (c ADCChannel) Pin() (p Pin, err error) { err = nil switch c { case adc0_CH: p = ADC0 case adc1_CH: p = ADC1 case adc2_CH: p = ADC2 case adc3_CH: p = ADC3 default: err = errors.New("no associated pin for channel") } return p, err }