Skip to content

Latest commit

 

History

History
213 lines (158 loc) · 5.58 KB

README_ADC.md

File metadata and controls

213 lines (158 loc) · 5.58 KB

Analog to digital conversion

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!

Doxygen documentation

The Adc subsystem is documented in the: namespace adc

Settings

When doing adc you have to specify

  • an input
  • a reference voltage
  • a mode

Input selection

In addition to input pins (see PIN_ADC* typedefs in README_PORTS.md Input::Temperature, Input::V1_1 and Input::Gnd are allowed inputs.

Reference voltages

Possible references are defined in:

enum class Ref {
  ARef = 0b00,
  AVcc = 0b01,
  V1_1 = 0b11
};

ADC modes

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.

Synchronous conversions

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.

Background conversions

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();

Adc Tasks and Adc Irq handler

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

Switching between different inputs, reference voltages or modes

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 Tasks, but all Tasks will be executed instead of only the one where the conversion was started!