The adc subsystem is abstracted through the Adc
class.
Default settings (input, reference voltage and mode) may be set as
template arguments of the Adc
class.
Calling the static method init()
will set the subsystem. The
init()
method also provides template arguments, making it possible to
override the default settings. Note that the order of those template
arguments differ!
The Adc subsystem is documented in the: namespace adc
When doing adc you have to specify
- an input
- a reference voltage
- a mode
In addition to input pins (see PIN_ADC*
typedef
s in
README_PORTS.md Input::Temperature
, Input::V1_1
and Input::Gnd
are allowed inputs.
Possible references are defined in:
enum class Ref {
ARef = 0b00,
AVcc = 0b01,
V1_1 = 0b11
};
Possible modes are defined in:
enum class Mode {
SingleConversion = 0xFF,
FreeRunning = 0b000,
TriggerAnalogComp = 0b001,
TriggerPCInt0 = 0b010,
TriggerTimer0CompA = 0b011,
TriggerTimer0OvF = 0b100,
TriggerTimer1CompB = 0b101,
TriggerTimer1OvF = 0b110,
TriggerTimer1Capt = 0b111
};
See the doxygen doc for short descriptions of those enum values.
adc_8bit()
or adc_10bit()
by default start a conversion and
busywait for the conversion to finish.
MyAdc::init<PIN_ADC0>();
uint8_t adc1 = MyAdc::adc_8bit();
MyAdc2::init<PIN_ADC1>();
uint16_t adc2 = MyAdc2::adc_10bit();
A non-zero template argument to adc_*bit()
will put the cpu into idle
sleep, which reduces noise and improves conversion results.
You have to register an ADC-irq handler if you want to use noise
reduction. (The default handler would reset the cpu and adc_*bit()
must enable irqs, so that the cpu leaves sleep mode after a
conversion.)
// use the default nullptr (do_nothing) irq-task
typedef Adc<Ref::V1_1> NoiseRedAdc;
#define NEW_ADC NoiseRedAdc
#include REGISTER_ADC
// main { ...
NoiseRedAdc::init<PIN_ADC3>();
uint16_t adcNoiseRed = NoiseRedAdc::adc_10bit<1>();
// ... } // end of main
#include REGISTER_IRQS
Your code will not compile (with a semi-useful error) if you don't register a handler.
While the adc-subsystem performs a conversion the cpu is free to do other stuff.
Everything mentioned in the previous synchronous section applies to the background conversions as well.
To start a conversion simply call start_adc_*bit()
. To find out if
the conversion is still running, use is_adc_finished()
.
busy_wait_adc_finished()
will loop until is_adc_finished()
returns
true.
get_adc_*bit_result()
will return the result.
AdcWTask::init();
sei();
AdcWTask::start_adc_8bit();
When a Task
(except for the nullptr
) is provided as template
argument to Adc
then init()
will enable the Adc irq and the
Task
will be called every time an Adc irq is raised (i.e. every
time a conversion finishes).
init()
will enable the global irq flag if a Task
is registered!
This behaviour can be changed by passing a GlobalIrq::
enum to
init()
.
The type of Task
is typedef void(* task)(const uint16_t&)
. A
function, which takes a 16bit result as argument. Even if you only use
8bit conversions compiler optimizations should generate (near) optimal
code.
The irq handler must be registered and all irq handlers enabled by including the corresponding header files:
void f(const uint16_t& result) {
PIN_B4::PORT = result > 0x0F;
}
// use the default nullptr (do_nothing) irq-task
typedef Adc<Ref::V1_1, PIN_ADC5, Mode::FreeRunning, f> AdcWTask;
#define NEW_ADC AdcWTask
#include REGISTER_ADC
// main { ...
+++ADC_TASK2[^, ]+++
// ... } // end of main
#include REGISTER_IRQS
The Adc class itself allows you to specify input, reference voltage and
mode as template arguments. It is however possible to override those
values when calling init()
.
If you want to change the input, reference voltage or mode, simply call
init()
with the new values as template arguments.
To make your intentions clearer in this case, you can use Input::Unset
as template argument for the Adc class, and only pass a real input when
calling init()
. This is however not necessary. The generated code
will be either equal or very similar.
Using another Adc class with different template arguments is another possibility. Remember however, that the adc subsystem is shared.
Here one class (typedef
) is used to sample two inputs.
typedef Adc<Ref::V1_1, Input::Unset> Adc1_1;
Adc1_1::init<PIN_ADC3>();
adc3 = Adc1_1::adc_8bit();
Adc1_1::init<PIN_ADC4>();
adc4 = Adc1_1::adc_8bit();
The same result using two classes:
typedef Adc<Ref::V1_1, PIN_ADC3> AdcAdc3;
typedef Adc<Ref::V1_1, PIN_ADC4> AdcAdc4;
AdcAdc3::init();
adc3 = AdcAdc3::adc_8bit();
AdcAdc4::init();
adc4 = AdcAdc4::adc_8bit();
Remember, the subsystem is shared:
AdcAdc3::init();
AdcAdc4::init();
// !!! THIS WON'T WORK !!!
// AdcAdc3::adc_8bit() will use PIN_ADC4 as input!
// AdcAdc4::init() switched the adc subsystem to input PIN_ADC4.
adc3 = AdcAdc3::adc_8bit();
adc4 = AdcAdc4::adc_8bit();
It is possible to register multiple Task
s, but all Task
s will
be executed instead of only the one where the conversion was started!