ADC
module reference#include <adc.h>Makefile :
MODULES+=adc
The ADC (Analog to Digital Converter) is a peripheral dedicated to measuring voltages. Common applications include measuring the output of an analog sensor, sampling the voltage of a battery, ...
The ADC consists of different independant channels. The number of channels is package-dependant :
If the signal read with the ADC is unstable or incoherent, make sure that Vcc is stable and noise-free. ADCs are especially sensitive to noise on the power supply.
This module doesn't implement all the features provided by the peripheral. For more information about what the peripheral is capable of, look at the Hacking section below.
Initialize the ADC controller. This is usually not required, since this function will be automatically called if necessary when you first enable a channel or make a measurement. However, if you want to customize the analog reference, you will need to do so by calling this function manually.
If you use an external analog reference (EXTERNAL_REFERENCE
or DAC_VOUT
), make sure to specify its voltage in mV as well. For VCC-related references (VCC_0625
and VCC_OVER_2
), simply indicate the VCC voltage if it is not the standard 3.3V. For INTERNAL_1V
, the correct value (vref=1000
) will always be used.
init()
will be called automatically. Calling this function is usually not required, because it is automatically called when you first make a measurement. However, it might be useful in order to make sure that the controller is already properly initialized before making the first measurement and therefore avoid the initialization overhead.Read the current raw value measured by the ADC on the given channel. This is a 12-bit value (between 0 and 4095) that can be interpreted according to the current configuration, especially the gain and analog reference.
If another channel number is provided in relativeTo
, the measurement will be performed differentially with channel
as the positive side and relativeTo
as the negative side. In this case, the returned value will be mapped using the following scale : 0=-Vref, 2047=0V, 4095=+Vref.
Read the current voltage measured by the ADC on the given channel, in mV. This function uses readRaw()
above but uses the current configuration (gain and voltage reference given in init()
) to calculate the actual voltage in mV.
If another channel number is provided in relativeTo
, the measurement will be performed differentially with channel
as the positive side and relativeTo
as the negative side. In this case, the returned value will be signed.
The current implementation only allows reading a single-ended or differential voltage with a customizable gain and analog reference. However, the ADC peripheral provides more powerful features such as :
Some settings could also be customized in the existing module, especially around the SEQCFG
register in readRaw()
.
#ifndef _ADC_H_ #define _ADC_H_ #include <stdint.h> #include "gpio.h" #include "error.h" // Analog to Digital Converter // This module is used to measure an analog voltage on a pin namespace ADC { // Peripheral memory space base address const uint32_t ADC_BASE = 0x40038000; // Registers addresses const uint32_t OFFSET_CR = 0x000; // Control Register const uint32_t OFFSET_CFG = 0x004; // Configuration Register const uint32_t OFFSET_SR = 0x008; // Status Register const uint32_t OFFSET_SCR = 0x00C; // Status Clear Register const uint32_t OFFSET_SEQCFG = 0x014; // Sequencer Configuration Register const uint32_t OFFSET_CDMA = 0x018; // Configuration Direct Memory Access Register const uint32_t OFFSET_TIM = 0x01C; // Timing Configuration Register const uint32_t OFFSET_ITIMER = 0x020; // Internal Timer Register const uint32_t OFFSET_WCFG = 0x024; // Window Monitor Configuration Register const uint32_t OFFSET_WTH = 0x028; // Window Monitor Threshold Configuration Register const uint32_t OFFSET_LCV = 0x02C; // Sequencer Last Converted Value Register const uint32_t OFFSET_IER = 0x030; // Interrupt Enable Register const uint32_t OFFSET_IDR = 0x034; // Interrupt Disable Register const uint32_t OFFSET_IMR = 0x038; // Interrupt Mask Register const uint32_t OFFSET_CALIB = 0x03C; // Calibration Register // Registers fields const uint32_t CR_SWRST = 0; const uint32_t CR_TSTOP = 1; const uint32_t CR_TSTART = 2; const uint32_t CR_STRIG = 3; const uint32_t CR_REFBUFEN = 4; const uint32_t CR_REFBUFDIS = 5; const uint32_t CR_EN = 8; const uint32_t CR_DIS = 9; const uint32_t CR_BGREQEN = 10; const uint32_t CR_BGREQDIS = 11; const uint32_t CFG_REFSEL = 1; const uint32_t CFG_SPEED = 4; const uint32_t CFG_CLKSEL = 6; const uint32_t CFG_PRESCAL = 8; const uint32_t SR_SEOC = 0; const uint32_t SR_EN = 24; const uint32_t SR_TBUSY = 25; const uint32_t SR_SBUSY = 26; const uint32_t SR_CBUSY = 27; const uint32_t SR_REFBUF = 28; const uint32_t SEQCFG_HWLA = 0; const uint32_t SEQCFG_BIPOLAR = 2; const uint32_t SEQCFG_GAIN = 4; const uint32_t SEQCFG_GCOMP = 7; const uint32_t SEQCFG_TRGSEL = 8; const uint32_t SEQCFG_RES = 12; const uint32_t SEQCFG_INTERNAL = 14; const uint32_t SEQCFG_MUXPOS = 16; const uint32_t SEQCFG_MUXNEG = 20; const uint32_t SEQCFG_ZOOMRANGE = 28; using Channel = uint8_t; enum class AnalogReference { INTERNAL_1V = 0b000, VCC_0625 = 0b001, // VCC * 0.625 EXTERNAL_REFERENCE = 0b010, DAC_VOUT = 0b011, VCC_OVER_2 = 0b100, // VCC / 2 }; enum class Gain { X1 = 0b000, X2 = 0b001, X4 = 0b010, X8 = 0b011, X16 = 0b100, X32 = 0b101, X64 = 0b110, X05 = 0b111, // divided by 2 }; // Module API void init(AnalogReference analogReference=AnalogReference::VCC_OVER_2, int vref=3300); void enable(Channel channel); void disable(Channel channel); uint16_t readRaw(Channel channel, Gain gain=Gain::X05, Channel relativeTo=0xFF); int read(Channel channel, Gain gain=Gain::X05, Channel relativeTo=0xFF); void setPin(Channel channel, GPIO::Pin pin); } #endif
#include "adc.h" #include "pm.h" namespace ADC { // Package-dependant, defined in pins_sam4l_XX.cpp extern struct GPIO::Pin PINS[]; // Bitset representing enabled channels uint16_t _enabledChannels = 0x0000; // Keep track of the state of the module bool _initialized = false; // Analog reference selected by the user AnalogReference _analogReference = AnalogReference::INTERNAL_1V; // Reference voltage value in mV // For VCC_0625 and VCC_OVER_2, this is simply the Vcc voltage int _vref = 0; // Initialize the common ressources of the ADC controller void init(AnalogReference analogReference, int vref) { // Voltage reference if (analogReference == AnalogReference::INTERNAL_1V) { vref = 1000; } _analogReference = analogReference; _vref = vref; // Enable the clock PM::enablePeripheralClock(PM::CLK_ADC); // Find a prescaler setting that will bring the module clock frequency below // the maximum specified in the datasheet (42.9.4 Analog to Digital Converter // Characteristics - ADC clock frequency - Max : 1.5MHz). // A frequency too high may otherwise result in incorrect conversions. unsigned long frequency = PM::getModuleClockFrequency(PM::CLK_ADC); uint8_t prescal = 0; for (prescal = 0b000; prescal <= 0b111; prescal++) { if ((frequency >> (prescal + 2)) <= 1500000) { // 1.5MHz break; } } // CR (Control Register) : enable the ADC (*(volatile uint32_t*)(ADC_BASE + OFFSET_CR)) = 1 << CR_EN // EN : enable ADC | 1 << CR_REFBUFEN // REFBUFEN : enable reference buffer | 1 << CR_BGREQEN; // BGREQEN : enable bandgap voltage reference // CFG (Configuration Register) : set general settings (*(volatile uint32_t*)(ADC_BASE + OFFSET_CFG)) = static_cast<int>(analogReference) << CFG_REFSEL // REFSEL : voltage reference | 0b11 << CFG_SPEED // SPEED : 75ksps | 1 << CFG_CLKSEL // CLKSEL : use APB clock | prescal << CFG_PRESCAL; // PRESCAL : divide clock by 4 // SR (Status Register) : wait for enabled status flag while (!((*(volatile uint32_t*)(ADC_BASE + OFFSET_SR)) & (1 << SR_EN))); // Update the module status _initialized = true; } void enable(Channel channel) { // Set the pin in peripheral mode GPIO::enablePeripheral(PINS[channel]); // Initialize and enable the ADC controller if necessary if (!_initialized) { init(); } _enabledChannels |= 1 << channel; } void disable(Channel channel) { // Disable the peripheral mode on the pin GPIO::disablePeripheral(PINS[channel]); // Disable the ADC controller if necessary _enabledChannels &= ~(uint32_t)(1 << channel); if (_enabledChannels == 0x0000) { // CR (Control Register) : disable the ADC (*(volatile uint32_t*)(ADC_BASE + OFFSET_CR)) = 1 << CR_DIS; // DIS : disable ADC _initialized = false; } } // Read the current raw value measured by the ADC on the given channel uint16_t readRaw(Channel channel, Gain gain, Channel relativeTo) { // Enable the channels if they are not already if (!(_enabledChannels & 1 << channel)) { enable(channel); } if (relativeTo != 0xFF && !(_enabledChannels & 1 << relativeTo)) { enable(relativeTo); } // SEQCFG (Sequencer Configuration Register) : setup the conversion (*(volatile uint32_t*)(ADC_BASE + OFFSET_SEQCFG)) = 0 << SEQCFG_HWLA // HWLA : Half Word Left Adjust disabled | (relativeTo != 0xFF) << SEQCFG_BIPOLAR // BIPOLAR : single-ended or bipolar mode | static_cast<int>(gain) << SEQCFG_GAIN // GAIN : user-selected gain | 1 << SEQCFG_GCOMP // GCOMP : gain error reduction enabled | 0b000 << SEQCFG_TRGSEL // TRGSEL : software trigger | 0 << SEQCFG_RES // RES : 12-bit resolution | (relativeTo != 0xFF ? 0b00 : 0b10) << SEQCFG_INTERNAL // INTERNAL : POS external, NEG internal or external | (channel & 0b1111) << SEQCFG_MUXPOS // MUXPOS : selected channel | (relativeTo != 0xFF ? relativeTo & 0b111 : 0b111) << SEQCFG_MUXNEG // MUXNEG : pad ground or neg channel | 0b000 << SEQCFG_ZOOMRANGE; // ZOOMRANGE : default // CR (Control Register) : start conversion (*(volatile uint32_t*)(ADC_BASE + OFFSET_CR)) = 1 << CR_STRIG; // STRIG : Sequencer Trigger // SR (Status Register) : wait for Sequencer End Of Conversion status flag while (!((*(volatile uint32_t*)(ADC_BASE + OFFSET_SR)) & (1 << SR_SEOC))); // SCR (Status Clear Register) : clear Sequencer End Of Conversion status flag (*(volatile uint32_t*)(ADC_BASE + OFFSET_SCR)) = 1 << SR_SEOC; // LCV (Last Converted Value) : conversion result return (*(volatile uint32_t*)(ADC_BASE + OFFSET_LCV)) & 0xFFFF; } // Return the current value on the given channel in mV int read(Channel channel, Gain gain, Channel relativeTo) { int value = readRaw(channel, gain, relativeTo); // Compute reference int vref = _vref; if (_analogReference == AnalogReference::VCC_0625) { vref = (vref * 625) / 1000; } else if (_analogReference == AnalogReference::VCC_OVER_2) { vref = vref / 2; } else if (_analogReference == AnalogReference::INTERNAL_1V) { vref = 1000; } // Convert the result to mV // Single-ended : value = gain * voltage / ref * 4095 <=> voltage = value * ref / (gain * 4095) // Differential : value = 2047 + gain * voltage / ref * 2047 <=> voltage = (value - 2047) * ref / (gain * 2047) if (relativeTo != 0xFF) { value -= 2047; } int gainCoefficients[] = {1, 2, 4, 8, 16, 32, 64}; if (gain == Gain::X05) { value *= 2; } value *= vref; if (relativeTo != 0xFF) { value /= 2047; } else { value /= 4095; } if (gain != Gain::X05) { value /= gainCoefficients[static_cast<int>(gain)]; } return value; } void setPin(Channel channel, GPIO::Pin pin) { PINS[channel] = pin; } }