ADC Access Failing when Optimized (-O != 0)

cancel
Showing results for 
Show  only  | Search instead for 
Did you mean: 

ADC Access Failing when Optimized (-O != 0)

709 Views
martinjaymckee
Contributor II

I'm trying to debug a strange error.  I have a program that is using ADC1.  I am working in C++.  The program works perfectly with optimization off. I have a fairly complicated class that wraps the ADC for configuration and access.  Again, no problems when optimizations is set to -O0.  When enable any level of optimization, however, all of the ADC channels immediately begin reading 0.  Here's what I know.

I have checked all the hardware registers that I can think of through the SWD debugger and the configuration seems to be identical with or without optimization (as expected).  I have checked SEQA-CTRL and its configuration is:

SEQA-ENA[31:31]ENABLED
MODE[30:30]END_OF_SEQUENCE
LOWPRIO[29:29]LOW_PRIORITY
SINGLESTEP[28:28]0x0
BURST[27:27]0x1
START[26:26]0x0
SYNCBYPASS[19:19]ENABLE_SYNCHRONIZATI
TRIGPOL[18:18]POSITIVE_EDGE
TRIGGER[15:12]0x0
CHANNELS[11:0]0x4b

Note that the burst bit is reading as active.  The ADC1.CTRL register is configured as:

CTRL0x400800000x7
CALMODE[30:30]0x0
LPWRMODE[10:10]DISABLED
MODE10BIT[9:9]DISABLED
ASYNMODE[8:8]SYNCHRONOUS_MODE
CLKDIV[7:0]0x7

So the clock divisor is getting correctly set.  Additionally, the clock is enabled in the SYSCON->SYSAHBCLKCTRL0 and the SWM->PINENABLE0 register has the correct input pins enabled.

Both the SEQA-GDAT and DATx registers show no valid data (all bits are clear).  I am at a loss.

Any further ideas what to check?  I rewrote just the ADC code without the C++ class and I'm seeing the results I would expect to see.  However, the configuration is -- again -- the same so far as I can tell (TRIGPOL is set to NEGATIVE_EDGE) but that shouldn't matter with BURST should it?  Any ideas what I could look into to try to solve this problem?

Thanks,

Martin Jay McKee

Labels (1)
4 Replies

530 Views
converse
Senior Contributor V

Suggest you read this FAQ, and use volatile in your hardware definition

https://community.nxp.com/thread/388981 

530 Views
martinjaymckee
Contributor II

Thank you for your thoughts.  I had already been playing with volatile as my assumption is certainly that some access is being optimized away.  While I already had before, after reading the FAQ I again tried to add volatile to all hardware accesses and the program continued to compile to the same size.  With or without volatile it isn't working however.

I have found some strange things though.  First, the working (direct register configuration) program compiles to 2508 Bytes,  My non-working (class-based) program compiles to 1968 Bytes.  That's a lot of code gone.  I can certainly believe that something is getting missed.  What remains strange, however, is that the configuration I see in the hardware when I single-step the program is identical.  It's just not running (for some reason) on the class version.  Is it possible that there is an ordering constraint that isn't being met? Or a timing constraint?  It seems like the memory access ARE happening since I can change the hardware configuration by changing my code.  I just can't get the ADC to run.

Thanks again,

Martin Jay McKee

0 Kudos

530 Views
converse
Senior Contributor V

I think you’ll have to show us the working and non working code (including definitions), if we are to help further.

0 Kudos

530 Views
martinjaymckee
Contributor II

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
{
//
// ADC ADCSources
//
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

        //
        // ADC Channel Proxy Class
        //
        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}; }

                // TODO: THIS CAST OPERATOR SHOULD BE CHANGED TO A COUNTS() METHOD WHICH RETURNS USING
                //     THE ADC VALUE_T AND SHOULD USE THE RAW() METHOD.
                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
                }

                // TODO: THIS SHOULD BE MADE PROTECTED AND SHOULD HOLD THE IMPLEMENTATION OF THE ADC READ
                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); // TODO: MAKE AN ADC WRAPPER CLASS FOR THIS???
                    return true;
                }

            private:
                ADC<value_t>& adc_;
                uint8_t chan_;
                uint16_t mask_;
                ADCSource src_;
        };

        //
        // ADC Block Implementation
        //
        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
            }
        }

        // TODO: THIS SHOULD BE CHANGED TO ACTUALLY INITIALIZE INTO THE CORRECT MODE ( INCLUDING TRIGGER )
        bool init( const chandra::chrono::frequency::rep& _f_adc = maximumADCClock()) {
            setADCClockDiv(_f_adc); // Defaults to the maximum allowable clock
            #if defined(__LPC82X__)
            adc_->SEQ_CTRL[0] |= (0b11<<30); // Enable the Sequence and Set to "End-Of-Sequence" Mode
            #elif defined(__LPC15XX__)
            //LPC_SYSCON->PDRUNCFG &= ~(1<<(10+num_)); //NOTE: THIS IS BEING DONE IN THE CONSTRUCTOR
            adc_->SEQ_CTRL[0] |= (0b11<<30); // Enable the Sequence and Set to "End-Of-Sequence" Mode
            #elif defined(__LPC84X__)
            //LPC_SYSCON->PDRUNCFG &= ~(1<<4);
            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; } // TODO: THIS NEEDS TO BE IMPLEMENTED
        bool valid() const { return true; } // TODO: THIS NEEDS TO BE IMPLEMENTED

        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)) {} // Loop until calibration done
                adc_->CTRL = ctrl; // Reset Control Register if blocking
            }

            return static_cast<bool>((adc_->CTRL & (1<<30)) == 0);
        }

        void sample(const bool& _blocking=false) { // NOTE: THE CURRENT BLOCKING IMPLEMENTATION IS A MAJOR HACK....
            #if defined(__LPC82X__) || defined(__LPC15XX__)
            adc_->SEQ_CTRL[0] |= (1<<26); // Enable the Sequence and Set to "End-Of-Sequence" Mode
            #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
        } // This may be a decent way to do this, but it's not clear yet

    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__) // TODO: CHECK 84X MAXIMUM CLOCK
                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_;
};

} /*namespace io*/

} /*namespace chandra*/
#endif /*CHANDRA_ADC_H*/
‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍

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

0 Kudos