From 9eb4a6268aa8d61de9d6c03d6d02c0f0c2be157f Mon Sep 17 00:00:00 2001 From: lincolngill Date: Tue, 26 Apr 2022 21:49:38 +1200 Subject: [PATCH] Pico adc input ch support (#2737) machine/rp2040: ADC changes, including * Add rp2040 ADC mux channel support. Internal temp sensor reading and fix for incorrect setting of CS.AINSEL reg bits * Reset ADC ref voltage in InitADC --- src/examples/adc_rp2040/adc.go | 49 +++++++++++ src/machine/machine_rp2040_adc.go | 134 ++++++++++++++++++++++++------ 2 files changed, 158 insertions(+), 25 deletions(-) create mode 100644 src/examples/adc_rp2040/adc.go diff --git a/src/examples/adc_rp2040/adc.go b/src/examples/adc_rp2040/adc.go new file mode 100644 index 00000000..ef0e2d40 --- /dev/null +++ b/src/examples/adc_rp2040/adc.go @@ -0,0 +1,49 @@ +// Reads multiple rp2040 ADC channels concurrently. Including the internal temperature sensor + +package main + +import ( + "fmt" + "machine" + "time" +) + +type celsius float32 + +func (c celsius) String() string { + return fmt.Sprintf("%4.1f℃", c) +} + +// rp2040 ADC is 12 bits. Reading are shifted <<4 to fill the 16-bit range. +var adcReading [3]uint16 + +func readADC(a machine.ADC, w time.Duration, i int) { + for { + adcReading[i] = a.Get() + time.Sleep(w) + } +} + +func main() { + machine.InitADC() + a0 := machine.ADC{machine.ADC0} // GPIO26 input + a1 := machine.ADC{machine.ADC1} // GPIO27 input + a2 := machine.ADC{machine.ADC2} // GPIO28 input + t := machine.ADC_TEMP_SENSOR // Internal Temperature sensor + // Configure sets the GPIOs to PinAnalog mode + a0.Configure(machine.ADCConfig{}) + a1.Configure(machine.ADCConfig{}) + a2.Configure(machine.ADCConfig{}) + // Configure powers on the temperature sensor + t.Configure(machine.ADCConfig{}) + + // Safe to read concurrently + go readADC(a0, 10*time.Millisecond, 0) + go readADC(a1, 17*time.Millisecond, 1) + go readADC(a2, 29*time.Millisecond, 2) + + for { + fmt.Printf("ADC0: %5d ADC1: %5d ADC2: %5d Temp: %v\n\r", adcReading[0], adcReading[1], adcReading[2], celsius(float32(t.ReadTemperature())/1000)) + time.Sleep(1000 * time.Millisecond) + } +} diff --git a/src/machine/machine_rp2040_adc.go b/src/machine/machine_rp2040_adc.go index c9631e39..97a54c67 100644 --- a/src/machine/machine_rp2040_adc.go +++ b/src/machine/machine_rp2040_adc.go @@ -5,58 +5,142 @@ 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 + ADC_TEMP_SENSOR // 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() { - // reset ADC 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 configures a ADC pin to be able to be used to read data. -func (a ADC) Configure(config ADCConfig) { - switch a.Pin { - case ADC0, ADC1, ADC2, ADC3: - a.Pin.Configure(PinConfig{Mode: PinAnalog}) - default: - // invalid ADC - return +// 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 { - rp.ADC.CS.SetBits(uint32(a.getADCChannel()) << rp.ADC_CS_AINSEL_Pos) + 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 or powers on the temperature sensor for ADC_TEMP_SENSOR. +// 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 + } + if p, err := c.Pin(); err == nil { + p.Configure(PinConfig{Mode: PinAnalog}) + } + if c == ADC_TEMP_SENSOR { + // Enable temperature sensor bias source + rp.ADC.CS.SetBits(rp.ADC_CS_TS_EN) + } + 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 uses 12-bit sampling, so scale to 16-bit - return uint16(rp.ADC.RESULT.Get() << 4) + // 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. +// Only works on the ADC_TEMP_SENSOR channel. aka AINSEL=4. Other channels will return 0 +func (c ADCChannel) ReadTemperature() (millicelsius uint32) { + if c != ADC_TEMP_SENSOR { + return + } + // T = 27 - (ADC_voltage - 0.706)/0.001721 + return (27000<<16 - (c.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) { } } -func (a ADC) getADCChannel() uint8 { - switch a.Pin { - case ADC0: - return 0 - case ADC1: - return 1 - case ADC2: - return 2 - case ADC3: - return 3 +// 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: - return 0 + err = errors.New("no associated pin for channel") } + return p, err }