Let's see what I can do. The code of the main function is quite simple, the definition of the ADC class is not -- unfortunately.
The main file of the functioning version is as follows:
#include <cr_section_macros.h>
#include <chrono>
#include <ratio>
using namespace std::chrono;
#include <chip.h>
int main(void) {
SystemCoreClockUpdate();
LPC_SYSCON->SYSAHBCLKCTRL[0] |= (1<<28);
LPC_SYSCON->PDRUNCFG &= ~(1UL<<11UL);
LPC_ADC1->CTRL = 7;
LPC_ADC1->INSEL = 1;
LPC_ADC1->SEQ_CTRL[0] = (1<<31) | (1<<30) | (1<<27) | (1<<6) | (1<<3) | (1<<1) | (1<<0);
while(true) {}
return 0 ;
}
As you can see, I simply setup the registers directly and when the program is debugged, I see the channels being sampled as expected.
The main file of the non-functioning (when compiled) version is as follows:
#include <cr_section_macros.h>
#include <chrono>
#include <ratio>
using namespace std::chrono;
#include <chip.h>
#include "adc.h"
using namespace chandra;
using namespace chandra::units::mks::literals;
int main(void) {
SystemCoreClockUpdate();
io::ADC<float> sensor_adc(1);
sensor_adc.init(10_MHz_);
auto Vcore = sensor_adc[0];
Vcore.src(io::ADCSource::CoreVoltage);
auto Vsense = sensor_adc[1];
auto Isense = sensor_adc[3];
auto Tsense = sensor_adc[6];
sensor_adc.burst();
while(true) {}
return 0 ;
}
This code generates the same configuration in the registers. There are a few additional things that it does, however. Line 18 configures the ADC clock and basic configuration. Lines 20 and 21 configure the channel 0 source and create an object -- Vcore -- which also provides access to the ADC results. The access, of course, is not demonstrated in this code. Lines 23-25 create access objects for channels 1, 3 and 6. A side effect of creating these objects is to enable reading these channels in the sequence control register. They are, therefore, equivalent to Line 16 in the functional version. Finally, Line 27 triggers the burst mode.
The ADC class is defined as follows (yeah... it's ugly):
#ifndef CHANDRA_ADC_H
#define CHANDRA_ADC_H
#include <chrono>
#include <limits>
using namespace std::chrono_literals;
#if defined(__LPC82X__) || defined(__LPC15XX__)
#include <chip.h>
#elif defined(__LPC84X__)
#include <LPC8xx.h>
#else
#error "Undefined processor type for ADC implementation."
#endif
#include "chrono.h"
#include "chip_utils.h"
namespace chandra
{
namespace io
{
enum class ADCSource
{
Pin = 0x00,
CoreVoltage = 0x01,
InternalReference = 0x02,
Temperature = 0x03,
HalfRail = 0x04
};
template<typename Value = float>
class ADC
{
public:
using value_t = Value;
#if defined(__LPC82X__) || defined(__LPC15XX__)
using adc_peripheral_t = LPC_ADC_T;
#elif defined(__LPC84X__)
using adc_peripheral_t = LPC_ADC_TypeDef;
#else
#error "ADC peripheral not defined for this processor type"
#endif
class ADCChannel
{
public:
using raw_value_t = uint16_t;
ADCChannel(ADC& _adc, const uint8_t& _chan, const bool& _enable = true ) : adc_(_adc), chan_(_chan), mask_(1UL<<_chan) {
if(_enable) enable(true);
}
bool enable(
const bool& _enabled = true,
const ADCSource& _src = ADCSource::Pin)
{
src(_src);
#if defined(__LPC15XX__)
if( _enabled ) {
adc_.adc_->SEQ_CTRL[0] |= mask_;
} else {
adc_.adc_->SEQ_CTRL[0] &= ~mask_;
}
#elif defined(__LPC82X__)
#elif defined(__LPC84X__)
if( _enabled ) {
adc_.adc_->SEQA_CTRL |= (1<<18) | mask_;
} else {
adc_.adc_->SEQA_CTRL &= ~mask_;
}
#endif
return _enabled;
}
bool channelEnabled() const {
#if defined(__LPC82X__) || defined(__LPC15XX__)
return adc_.adc_->SEQ_CTRL[0] & mask_;
#elif defined(__LPC84X__)
return adc_.adc_->SEQA_CTRL & mask_;
#endif
}
bool enabled() const {
return adc_.enabled() && channelEnabled();
}
bool src(const ADCSource& _src) {
if(_src == ADCSource::Pin) {
enablePin(true);
src_ = _src;
return true;
} else if(chan_ != 0){
return false;
}
enablePin(false);
#if defined(__LPC15XX__)
adc_.adc_->INSEL = uint32_t(_src);
#else
return false;
#endif
src_ = _src;
return true;
}
uint8_t channel() const { return chan_; }
ADCSource src() const { return src_; }
static constexpr value_t min() noexcept { return Value{0}; }
static constexpr value_t max() noexcept { return Value{0xFFF0}; }
template<class T>
operator T () const {
#if defined(__LPC82X__) || defined(__LPC15XX__)
return static_cast<T>(adc_.adc_->DR[chan_]&0x0000FFF0UL);
#elif defined(__LPC84X__)
return static_cast<T>(adc_.adc_->DAT[chan_]&0x0000FFF0UL);
#endif
}
raw_value_t raw() const { return this->operator raw_value_t(); }
protected:
bool enablePin(const bool& _enable) {
#if defined(__LPC82X__)
const auto bit_offset = 13;
#elif defined(__LPC84X__)
const auto bit_offset = 14;
#elif defined(__LPC15XX__)
const auto bit_offset = adc_.num() * 12;
#endif
FixedFunctionIO::enable(0, chan_ + bit_offset, _enable);
return true;
}
private:
ADC<value_t>& adc_;
uint8_t chan_;
uint16_t mask_;
ADCSource src_;
};
ADC(const uint8_t& _num) : adc_(getADC(_num)), num_(_num) {
switch(num_) {
#if defined(__LPC82X__) || defined(__LPC84X__)
default:
case 0:
SystemClock::enable(0, 24);
PeripheralActivity::reset(0, 24);
PowerConfiguration::enable(0, 4);
break;
#elif defined(__LPC15XX__)
case 0:
SystemClock::enable(0, 27);
PeripheralActivity::reset(0, 27);
PowerConfiguration::enable(0, 10);
break;
case 1:
SystemClock::enable(0, 28);
PeripheralActivity::reset(0, 28);
PowerConfiguration::enable(0, 11);
break;
#endif
}
}
bool init( const chandra::chrono::frequency::rep& _f_adc = maximumADCClock()) {
setADCClockDiv(_f_adc);
#if defined(__LPC82X__)
adc_->SEQ_CTRL[0] |= (0b11<<30);
#elif defined(__LPC15XX__)
adc_->SEQ_CTRL[0] |= (0b11<<30);
#elif defined(__LPC84X__)
adc_->SEQA_CTRL |= (0b11<<30);
#endif
calibrate();
return valid();
}
uint8_t num() const { return num_; }
ADCChannel operator [] ( const uint8_t _chan ) { return ADCChannel(*this, _chan); }
bool enabled() const { return true; }
bool valid() const { return true; }
bool calibrate(bool _blocking = true) {
const uint32_t ctrl = adc_->CTRL;
const uint32_t div = calcADCClockDiv(
500000UL,
chandra::chrono::frequency::adc(num_).value()) - 1UL;
adc_->CTRL = (1<<30) | div;
if( _blocking ) {
while( (adc_->CTRL & (1<<30)) == (1<<30)) {}
adc_->CTRL = ctrl;
}
return static_cast<bool>((adc_->CTRL & (1<<30)) == 0);
}
void sample(const bool& _blocking=false) {
#if defined(__LPC82X__) || defined(__LPC15XX__)
adc_->SEQ_CTRL[0] |= (1<<26);
#elif defined(__LPC84X__)
adc_->SEQA_CTRL |= (1<<26);
#endif
if(_blocking) chandra::chrono::delay(100us);
}
void burst() {
#if defined(__LPC82X__) || defined(__LPC15XX__)
adc_->SEQ_CTRL[0] |= (1<<27);
#elif defined(__LPC84X__)
adc_->SEQA_CTRL |= (1<<27);
#endif
}
protected:
static adc_peripheral_t* getADC(const uint8_t& _num) {
switch(_num) {
#if defined(__LPC82X__)
default:
case 0:
return LPC_ADC;
#elif defined(__LPC84X__)
default:
case 0:
return LPC_ADC;
#elif defined(__LPC15XX__)
case 1:
return LPC_ADC1;
default:
case 0:
return LPC_ADC0;
#endif
}
}
uint32_t setADCClockDiv( const chandra::chrono::frequency::rep& _f_adc, const chandra::chrono::frequency::rep& _f_cpu ) {
const auto f_adc = (_f_adc > maximumADCClock()) ? maximumADCClock() : _f_adc;
const uint32_t div = calcADCClockDiv(f_adc.value(), _f_cpu.value()) - 1UL;
adc_->CTRL = (adc_->CTRL&0xFFFFF00) | div;
return (_f_cpu.value() + ( (div+1) / 2 ) ) / (div+1);
}
uint32_t setADCClockDiv( const chandra::chrono::frequency::rep& _f_adc ) {
return setADCClockDiv(_f_adc, chandra::chrono::frequency::main());
}
static chandra::chrono::frequency::rep maximumADCClock() {
#if defined(__LPC82X__) || defined(__LPC84X__)
return chandra::chrono::frequency::rep{30000000};
#elif defined(__LPC15XX__)
return chandra::chrono::frequency::rep{50000000};
#endif
}
static uint8_t calcADCClockDiv(
const uint32_t& _f_adc,
const uint32_t& _f_cpu )
{
const uint32_t base_div = (_f_cpu / _f_adc);
const uint32_t div = base_div + (((base_div * _f_adc) < _f_cpu) ? 1 : 0);
return (div > 255) ? 255 : div;
}
private:
volatile adc_peripheral_t* adc_;
uint8_t num_;
};
}
}
#endif
The ADC code uses some other library functions (for instance Lines 168-170) which are tested to work. They just implement some basic register access functionality.
As it stands, the code size makes more sense, the functional version is smaller following optimization. That makes sense because there should be more register accesses in the class based version. When I debug the two versions, the configuration registers are showing the same settings. Changing the configuration through the C++ code changes the register values appropriately. There is no access to the ADC happening in the second version (with the class) however.
Apologies for the mass of code.
Cheers,
Martin Jay McKee