From 9d6df2b4c787460f846cdb5040d9e24281a47b51 Mon Sep 17 00:00:00 2001 From: Ron Evans Date: Wed, 6 Mar 2019 13:59:21 +0100 Subject: [PATCH] machine/samd21: implement ADC Signed-off-by: Ron Evans --- src/machine/machine_atsamd21.go | 147 ++++++++++++++++++++++++++++++++ src/runtime/runtime_atsamd21.go | 12 +++ 2 files changed, 159 insertions(+) diff --git a/src/machine/machine_atsamd21.go b/src/machine/machine_atsamd21.go index b3dc1b6f..aa466b6c 100644 --- a/src/machine/machine_atsamd21.go +++ b/src/machine/machine_atsamd21.go @@ -188,6 +188,18 @@ func (p GPIO) Configure(config GPIOConfig) { } // enable port config p.setPinCfg(sam.PORT_PINCFG0_PMUXEN) + case GPIO_ANALOG: + if p.Pin&1 > 0 { + // odd pin, so save the even pins + val := p.getPMux() & sam.PORT_PMUX0_PMUXE_Msk + p.setPMux(val | (GPIO_ANALOG << sam.PORT_PMUX0_PMUXO_Pos)) + } else { + // even pin, so save the odd pins + val := p.getPMux() & sam.PORT_PMUX0_PMUXO_Msk + p.setPMux(val | (GPIO_COM << sam.PORT_PMUX0_PMUXE_Pos)) + } + // enable port config + p.setPinCfg(sam.PORT_PINCFG0_PMUXEN | sam.PORT_PINCFG0_DRVSTR) } } @@ -258,6 +270,141 @@ func (p GPIO) setPinCfg(val sam.RegValue8) { setPinCfg(p.Pin, val) } +// InitADC initializes the ADC. +func InitADC() { + // ADC Bias Calibration + // #define ADC_FUSES_BIASCAL_ADDR (NVMCTRL_OTP4 + 4) + // #define ADC_FUSES_BIASCAL_Pos 3 /**< \brief (NVMCTRL_OTP4) ADC Bias Calibration */ + // #define ADC_FUSES_BIASCAL_Msk (0x7u << ADC_FUSES_BIASCAL_Pos) + // #define ADC_FUSES_BIASCAL(value) ((ADC_FUSES_BIASCAL_Msk & ((value) << ADC_FUSES_BIASCAL_Pos))) + // #define ADC_FUSES_LINEARITY_0_ADDR NVMCTRL_OTP4 + // #define ADC_FUSES_LINEARITY_0_Pos 27 /**< \brief (NVMCTRL_OTP4) ADC Linearity bits 4:0 */ + // #define ADC_FUSES_LINEARITY_0_Msk (0x1Fu << ADC_FUSES_LINEARITY_0_Pos) + // #define ADC_FUSES_LINEARITY_0(value) ((ADC_FUSES_LINEARITY_0_Msk & ((value) << ADC_FUSES_LINEARITY_0_Pos))) + // #define ADC_FUSES_LINEARITY_1_ADDR (NVMCTRL_OTP4 + 4) + // #define ADC_FUSES_LINEARITY_1_Pos 0 /**< \brief (NVMCTRL_OTP4) ADC Linearity bits 7:5 */ + // #define ADC_FUSES_LINEARITY_1_Msk (0x7u << ADC_FUSES_LINEARITY_1_Pos) + // #define ADC_FUSES_LINEARITY_1(value) ((ADC_FUSES_LINEARITY_1_Msk & ((value) << ADC_FUSES_LINEARITY_1_Pos))) + + biasFuse := *(*uint32)(unsafe.Pointer(uintptr(0x00806020) + 4)) + bias := sam.RegValue16(uint16(biasFuse>>3) & uint16(0x7)) + + // ADC Linearity bits 4:0 + linearity0Fuse := *(*uint32)(unsafe.Pointer(uintptr(0x00806020))) + linearity := sam.RegValue16(uint16(linearity0Fuse>>27) & uint16(0x1f)) + + // ADC Linearity bits 7:5 + linearity1Fuse := *(*uint32)(unsafe.Pointer(uintptr(0x00806020) + 4)) + linearity |= sam.RegValue16(uint16(linearity1Fuse)&uint16(0x7)) << 5 + + // set calibration + sam.ADC.CALIB = (bias << 8) | linearity + + // Wait for synchronization + waitADCSync() + + // Divide Clock by 32 with 12 bits resolution as default + sam.ADC.CTRLB = (sam.ADC_CTRLB_PRESCALER_DIV32 << sam.ADC_CTRLB_PRESCALER_Pos) | + (sam.ADC_CTRLB_RESSEL_12BIT << sam.ADC_CTRLB_RESSEL_Pos) + + // Sampling Time Length + sam.ADC.SAMPCTRL = 5 + + // Wait for synchronization + waitADCSync() + + // Use internal ground + sam.ADC.INPUTCTRL = (sam.ADC_INPUTCTRL_MUXNEG_GND << sam.ADC_INPUTCTRL_MUXNEG_Pos) + + // Averaging (see datasheet table in AVGCTRL register description) + sam.ADC.AVGCTRL = (sam.ADC_AVGCTRL_SAMPLENUM_1 << sam.ADC_AVGCTRL_SAMPLENUM_Pos) | + (0x0 << sam.ADC_AVGCTRL_ADJRES_Pos) + + // Analog Reference is AREF pin (3.3v) + sam.ADC.INPUTCTRL |= (sam.ADC_INPUTCTRL_GAIN_DIV2 << sam.ADC_INPUTCTRL_GAIN_Pos) + + // 1/2 VDDANA = 0.5 * 3V3 = 1.65V + sam.ADC.REFCTRL |= (sam.ADC_REFCTRL_REFSEL_INTVCC1 << sam.ADC_REFCTRL_REFSEL_Pos) +} + +// Configure configures a ADCPin to be able to be used to read data. +func (a ADC) Configure() { + GPIO{a.Pin}.Configure(GPIOConfig{Mode: GPIO_ANALOG}) + return +} + +// Get returns the current value of a ADC pin, in the range 0..0xffff. +func (a ADC) Get() uint16 { + ch := a.getADCChannel() + + // Selection for the positive ADC input + sam.ADC.INPUTCTRL &^= sam.ADC_INPUTCTRL_MUXPOS_Msk + waitADCSync() + sam.ADC.INPUTCTRL |= sam.RegValue(ch << sam.ADC_INPUTCTRL_MUXPOS_Pos) + waitADCSync() + + // Enable ADC + sam.ADC.CTRLA |= sam.ADC_CTRLA_ENABLE + waitADCSync() + + // Start conversion + sam.ADC.SWTRIG |= sam.ADC_SWTRIG_START + waitADCSync() + + // Clear the Data Ready flag + sam.ADC.INTFLAG = sam.ADC_INTFLAG_RESRDY + waitADCSync() + + // Start conversion again, since first conversion after reference voltage changed is invalid. + sam.ADC.SWTRIG |= sam.ADC_SWTRIG_START + waitADCSync() + + // Waiting for conversion to complete + for (sam.ADC.INTFLAG & sam.ADC_INTFLAG_RESRDY) == 0 { + } + val := sam.ADC.RESULT + + // Disable ADC + sam.ADC.CTRLA &^= sam.ADC_CTRLA_ENABLE + waitADCSync() + + return uint16(val) +} + +func (a ADC) getADCChannel() uint8 { + switch a.Pin { + case PA02: + return 0 + case PB08: + return 2 + case PB09: + return 3 + case PA04: + return 4 + case PA05: + return 5 + case PA06: + return 6 + case PA07: + return 7 + case PB02: + return 10 + case PB03: + return 11 + case PA09: + return 17 + case PA11: + return 19 + default: + return 0 + } +} + +func waitADCSync() { + for (sam.ADC.STATUS & sam.ADC_STATUS_SYNCBUSY) > 0 { + } +} + // UART on the SAMD21. type UART struct { Buffer *RingBuffer diff --git a/src/runtime/runtime_atsamd21.go b/src/runtime/runtime_atsamd21.go index 92b0a442..bbbccbe5 100644 --- a/src/runtime/runtime_atsamd21.go +++ b/src/runtime/runtime_atsamd21.go @@ -24,6 +24,7 @@ func init() { initRTC() initSERCOMClocks() initUSBClock() + initADCClock() // connect to USB CDC interface machine.UART0.Configure(machine.UARTConfig{}) @@ -355,3 +356,14 @@ func initUSBClock() { sam.GCLK_CLKCTRL_CLKEN) waitForSync() } + +func initADCClock() { + // Turn on clock for ADC + sam.PM.APBCMASK |= sam.PM_APBCMASK_ADC_ + + // Put Generic Clock Generator 0 as source for Generic Clock Multiplexer for ADC. + sam.GCLK.CLKCTRL = sam.RegValue16((sam.GCLK_CLKCTRL_ID_ADC << sam.GCLK_CLKCTRL_ID_Pos) | + (sam.GCLK_CLKCTRL_GEN_GCLK0 << sam.GCLK_CLKCTRL_GEN_Pos) | + sam.GCLK_CLKCTRL_CLKEN) + waitForSync() +}