element14 Community
element14 Community
    Register Log In
  • Site
  • Search
  • Log In Register
  • Community Hub
    Community Hub
    • What's New on element14
    • Feedback and Support
    • Benefits of Membership
    • Personal Blogs
    • Members Area
    • Achievement Levels
  • Learn
    Learn
    • Ask an Expert
    • eBooks
    • element14 presents
    • Learning Center
    • Tech Spotlight
    • STEM Academy
    • Webinars, Training and Events
    • Learning Groups
  • Technologies
    Technologies
    • 3D Printing
    • FPGA
    • Industrial Automation
    • Internet of Things
    • Power & Energy
    • Sensors
    • Technology Groups
  • Challenges & Projects
    Challenges & Projects
    • Design Challenges
    • element14 presents Projects
    • Project14
    • Arduino Projects
    • Raspberry Pi Projects
    • Project Groups
  • Products
    Products
    • Arduino
    • Avnet Boards Community
    • Dev Tools
    • Manufacturers
    • Multicomp Pro
    • Product Groups
    • Raspberry Pi
    • RoadTests & Reviews
  • Store
    Store
    • Visit Your Store
    • Choose another store...
      • Europe
      •  Austria (German)
      •  Belgium (Dutch, French)
      •  Bulgaria (Bulgarian)
      •  Czech Republic (Czech)
      •  Denmark (Danish)
      •  Estonia (Estonian)
      •  Finland (Finnish)
      •  France (French)
      •  Germany (German)
      •  Hungary (Hungarian)
      •  Ireland
      •  Israel
      •  Italy (Italian)
      •  Latvia (Latvian)
      •  
      •  Lithuania (Lithuanian)
      •  Netherlands (Dutch)
      •  Norway (Norwegian)
      •  Poland (Polish)
      •  Portugal (Portuguese)
      •  Romania (Romanian)
      •  Russia (Russian)
      •  Slovakia (Slovak)
      •  Slovenia (Slovenian)
      •  Spain (Spanish)
      •  Sweden (Swedish)
      •  Switzerland(German, French)
      •  Turkey (Turkish)
      •  United Kingdom
      • Asia Pacific
      •  Australia
      •  China
      •  Hong Kong
      •  India
      •  Korea (Korean)
      •  Malaysia
      •  New Zealand
      •  Philippines
      •  Singapore
      •  Taiwan
      •  Thailand (Thai)
      • Americas
      •  Brazil (Portuguese)
      •  Canada
      •  Mexico (Spanish)
      •  United States
      Can't find the country/region you're looking for? Visit our export site or find a local distributor.
  • Translate
  • Profile
  • Settings
Test & Tools
  • Technologies
  • More
Test & Tools
Documents Programmable Electronic Load - ADC Firmware
  • Blog
  • Forum
  • Documents
  • Files
  • Members
  • Mentions
  • Sub-Groups
  • Tags
  • More
  • Cancel
  • New
Join Test & Tools to participate - click to join for free!
Actions
  • Share
  • More
  • Cancel
Engagement
  • Author Author: Jan Cumps
  • Date Created: 3 May 2020 10:24 AM Date Created
  • Last Updated Last Updated: 6 Oct 2021 10:20 PM
  • Views 2121 views
  • Likes 5 likes
  • Comments 17 comments
Related
Recommended

Programmable Electronic Load - ADC Firmware

This post documents the ADC firmware for the electronic load we made here on element14.

image

 

ADC

 

image

 

ADC samples are taken all the time, based on a TI-RTOS schedule.

Data is written to a round-robin buffer with 2 buckets.

 

volatile ADCValues adcRoundRobin[2];
volatile uint32_t adcRoundRobinIndex[ADC_ACTIVE_INPUTS] = {0};

 

One bucket has stable data and can be read whenever needed. A read pointer points to that stable bucket.

The other bucket is used to write sample data. Once samples from 4 channels are collected, the read pointer is toggled so that it points to that fresh data.

I've optimised this. I've added a read pointer for each channel now:

 

volatile uint32_t adcRoundRobinIndex[ADC_ACTIVE_INPUTS] = {0};

 

Once a channel is collected, the read pointer for that channel is toggled so that it points to the fresh data.

 

 

void *threadADC(void *arg0) {
    uint32_t i;


    a_i2cTransaction.writeBuf = a_txBuffer;
    a_i2cTransaction.readBuf = a_rxBuffer;
    a_i2cTransaction.slaveAddress = ADC_I2C_ADDR;


    // this buffer value never changes. Let's set it at the start.
    // If for some reason this becomes a variable value,
    // move to sampleADC()
    a_txBuffer[2] = ADS1115_CFG_L;


    while (1)
    {
        for (i =0; i< ADC_ACTIVE_INPUTS; i++) {
            // we write value to the inactive robin
            // store value of ADC[i]
            // the ADC needs time between channel selection and sampling
            // we assign 1/ADC_ACTIVE_INPUTS of the task sleep time to
            // each of the ADC_ACTIVE_INPUTS samples
            // this puts more burden on the RTOS switcher - a compromise
            // - but certainly preferable to a loop
            // (except when later on we find out that the wait is only a few cpu cycles)
            adcRoundRobin[adcRoundRobinIndex[i] ? 0 : 1].raw[i] = sampleADC(i, THREAD_USLEEP_ADC / ADC_ACTIVE_INPUTS);
            // after value(s) written, we activate the inactive robin
            adcRoundRobinIndex[i] = adcRoundRobinIndex[i] ? 0 : 1;
        }

    }
}

 

Because there is time needed between switching ADC channels and taking the sample, we give each of the four ADC channels 1/4th of the TI-RTOS schedule that they can spend on that time.

 

x = sampleADC(i, (UInt)arg0/4);

 

Sampling is done via I²C. First part is switching the ADC channel. Then a sleep to give the ADC time, then fetch:

 

uint16_t sampleADC(uint32_t uModule, UInt uSleep) {
    uint16_t uRetval = 0u;

    // ...


     a_txBuffer[1] = array_ADS1115_CFG_H[uModule];

    // ...

    /* Init ADC and Start Sampling */
    if (! I2C_transfer(i2c_implGetHandle(), &a_i2cTransaction)){
        System_printf("Sampling Start Failed \n");
    }

    // there's a pause required between channel selection and data retrieval
    // we consume that part of the task sleep time that's assigned to us by the task.
    Task_sleep(uSleep);

    // ...

    /* Read ADC */
    if (I2C_transfer(i2c_implGetHandle(), &a_i2cTransaction)) {
        uRetval = ((a_rxBuffer[0] << 8) | a_rxBuffer[1]);
    }
    else {
        System_printf("ADC Read I2C Bus fault\n");
    }

    return uRetval;
}

 

Reading is done via a helper function - exposed as API to the program. This can be used anytime without locking or semaphore, because the time between switching the read pointer and the next possible write in that buffer is way longer (several orders of magnitude) than calling the helper function is an atomic command.

 

uint16_t adcImplGetAdc(uint32_t uModule) {
    return adcRoundRobin[adcRoundRobinIndex[uModule]].raw[uModule];
}

  • Share
  • History
  • More
  • Cancel
  • Sign in to reply

Top Comments

  • Andrew J
    Andrew J over 5 years ago in reply to Jan Cumps +3
    This will be interesting - I'm creating ADC interaction with Arduino code. Have you characterised the Zero Scale Error, Full Scale Error, Offset and Gain? It would seem from my brief checking of data sheets…
  • Jan Cumps
    Jan Cumps over 5 years ago in reply to Andrew J +3
    Have you characterised the Zero Scale Error, Full Scale Error, Offset and Gain? It would seem from my brief checking of data sheets (small sample) that ADCs are better than DACs in these specs but can…
  • Jan Cumps
    Jan Cumps over 5 years ago +2
    I'm going to try and improve this part of the firmware. At this moment it's a round-robin system that has data that's between 333 ms and 1s old. That's an eternity. Code is here: https://github.com/jancumps…
Parents
  • Jan Cumps
    Jan Cumps over 5 years ago

    My first remarks, after reading the datasheet and looking at my own code:

     

    uint16_t sampleADC(uint32_t uModule, uint32_t uSleep) {
        uint16_t uRetval = 0u;
    
        /* Point to the ADC ASD1115 and read input uModule */
        a_i2cTransaction.writeCount = 3;
        a_i2cTransaction.readCount = 0;
        a_txBuffer[0] = 0x01;
        a_txBuffer[1] = array_ADS1115_CFG_H[uModule];
    
        /* Init ADC and Start Sampling */
        if (! I2C_transfer(i2c_implGetHandle(), &a_i2cTransaction)){
    //        System_printf("Sampling Start Failed \n");
        }
    
    
        // there's a pause required between channel selection and data retrieval
        // we consume that part of the task sleep time that's assigned to us by the task.
        usleep(uSleep);
    
        a_txBuffer[0] = 0x00;
        a_i2cTransaction.writeCount = 1;
        a_i2cTransaction.readCount = 2;
        /* Read ADC */
        if (I2C_transfer(i2c_implGetHandle(), &a_i2cTransaction)) {
            uRetval = ((a_rxBuffer[0] << 8) | a_rxBuffer[1]);
        }
        else {
    //        System_printf("ADC Read I2C Bus fault\n");
        }
    
        return uRetval;
    }

     

    At each sample attempt, I

    • configure a channel
    • wait
    • sample the channel

     

    image

     

    While I think I can move this one to the initial setup, outside the sample loop:

    • configure a channel

     

    I can skip this one

    • wait

     

    And keep only this inside the loop:

    • sample the channel

     

    It's a little late in the evening to test this. But if that's it, it would be an easy change ...

    • Cancel
    • Vote Up +1 Vote Down
    • Sign in to reply
    • More
    • Cancel
  • Jan Cumps
    Jan Cumps over 5 years ago in reply to Jan Cumps

    Untested, but compiles:

     

    I extracted the setup and moved it out of the sample function, together with everything in the Tx buffer that's constant over the lifecycle:

     

        /* Init ADC and Start Sampling */
        a_i2cTransaction.writeCount = 3;
        a_i2cTransaction.readCount = 0;
        a_txBuffer[0] = 0x01;
        a_txBuffer[2] = ADS1115_CFG_L;
    
        for (i =0; i< ADC_ACTIVE_INPUTS; i++) {
          a_txBuffer[1] = array_ADS1115_CFG_H[i];
          if (! I2C_transfer(i2c_implGetHandle(), &a_i2cTransaction)){
            //        System_printf("Sampling Start Failed \n");
          }
        }
    
        a_txBuffer[0] = 0x00;
        a_i2cTransaction.writeCount = 1;
        a_i2cTransaction.readCount = 2;

     

    Then, the only part of ADC traffic that's in the loop is a call of this function per channel (this is all that's left from the function in the previous comment):

     

    uint16_t sampleADC(uint32_t uModule) {
        uint16_t uRetval = 0u;
    
        a_txBuffer[1] = array_ADS1115_CFG_H[uModule];
    
        /* Read ADC */
        if (I2C_transfer(i2c_implGetHandle(), &a_i2cTransaction)) {
            uRetval = ((a_rxBuffer[0] << 8) | a_rxBuffer[1]);
        }
        else {
    //        System_printf("ADC Read I2C Bus fault\n");
        }
    
        return uRetval;
    }

     

    I'll post back when I have the time to test. This may very well not work...

    I think I have to switch to single shot for the type of work I want to do ...

     

     

    edit: the longer I think about this, the less feasable:

    There's no automatic internal control of the input MUX, so I still have to call that set function to make the sampler switch channel then sample.

    Adafruit API waits 9 ms between sample and read (and they use single shot).

     

    The datasheet says:

    image

     

    There's a pin that tells what a sample is ready. It's unused in the eLoad.

    I didn't want to tie it to  a microcontroller pin, because that would break the isolation that we've designed.

     

    I could have attached it to one of the pins of the i2c port extender, in hindsight.

    .....

     

    image

    • Cancel
    • Vote Up +1 Vote Down
    • Sign in to reply
    • More
    • Cancel
  • Jan Cumps
    Jan Cumps over 5 years ago in reply to Jan Cumps

    Setup: a simple voltage divider that puts a different level on each ADC:

     

    image

    Levels:

     

    image

     

     

    I'll now debug through the code, check if what the ADC reports is actually for the correct input.

    My prediction, with the code above, is: no ...

    • Cancel
    • Vote Up +1 Vote Down
    • Sign in to reply
    • More
    • Cancel
  • Jan Cumps
    Jan Cumps over 5 years ago in reply to Jan Cumps

    Jan Cumps  wrote:

     

    ...

    I'll post back when I have the time to test. This may very well not work...

    I think I have to switch to single shot for the type of work I want to do ...

     

     

    edit: the longer I think about this, the less feasable:

    There's no automatic internal control of the input MUX, so I still have to call that set function to make the sampler switch channel then sample.

    Adafruit API waits 9 ms between sample and read (and they use single shot).

     

    ...

    Confirmed. I only get the sample value from the last channel I configured. Whatever Channel I read back.

     

    image

    So this was too naive. Now moving on to the Adafruit approach .....

    • Cancel
    • Vote Up 0 Vote Down
    • Sign in to reply
    • More
    • Cancel
  • Jan Cumps
    Jan Cumps over 5 years ago in reply to Jan Cumps

    I have the solution.

    I compared Adafruit's init settings with mine, and the only difference is that they have the device operating mode to one shot, and I to continuous.

    When I set  one-shot too, a reliable sample, including switching the channel, can be retrieved at 9 ms.

     

    image

    Excellent news.

     

    image

    image

     

    The channel configuration is now (last bit from 0 to 1):

    // single conversion mode, +- 6.144V
    #define ADS1115_CFG_H0 0b11000001
    #define ADS1115_CFG_H1 0b11010001
    #define ADS1115_CFG_H2 0b11100001
    #define ADS1115_CFG_H3 0b11110001

     

     

    I need the 9 ms in the sample function, so I defined that fixed:

     

    #define ADS1115_MUX_DELAY 9000
    // ...

     

    In the sample function, the delay is put between MUXIN and reading the result:

     

    uint16_t sampleADC(uint32_t uModule) {
        uint16_t uRetval = 0u;
    
        /* Init ADC and Start Sampling */
        a_txBuffer[1] = array_ADS1115_CFG_H[uModule];
    
        a_txBuffer[0] = 0x01;
        a_i2cTransaction.writeCount = 3;
        a_i2cTransaction.readCount = 0;
        if (! I2C_transfer(i2c_implGetHandle(), &a_i2cTransaction)){
          //        System_printf("Sampling Start Failed \n");
        }
    
        // inspiration: https://github.com/adafruit/Adafruit_ADS1X15/blob/master/Adafruit_ADS1015.cpp
        usleep(ADS1115_MUX_DELAY);
    
        /* Read ADC */
        a_txBuffer[0] = 0x00;
        a_i2cTransaction.writeCount = 1;
        a_i2cTransaction.readCount = 2;
        if (I2C_transfer(i2c_implGetHandle(), &a_i2cTransaction)) {
            uRetval = ((a_rxBuffer[0] << 8) | a_rxBuffer[1]);
        }     else {
    //        System_printf("ADC Read I2C Bus fault\n");
        }
    
        return uRetval;
    }

     

    In the RTOS job, I deduct the time idled in the sample function :

     

    void *threadADC(void *arg0) {
        uint32_t i;
    
        a_i2cTransaction.writeBuf = a_txBuffer;
        a_i2cTransaction.readBuf = a_rxBuffer;
        a_i2cTransaction.slaveAddress = ADC_I2C_ADDR;
    
        a_txBuffer[2] = ADS1115_CFG_L;
    
        while (1)
        {
            for (i =0; i< ADC_ACTIVE_INPUTS; i++) {
                // we write value to the inactive robin
                // store value of ADC[i]
                adcRoundRobin[adcRoundRobinIndex[i] ? 0 : 1].raw[i] = sampleADC(i);
                // after value(s) written, we activate the inactive robin
                adcRoundRobinIndex[i] = adcRoundRobinIndex[i] ? 0 : 1;
            }
    
            // the thread delays ADS1115_MUX_DELAY per sample
            // so let's deduct the total delay from the requested sleep time
            usleep(THREAD_USLEEP_ADC - (ADS1115_MUX_DELAY * ADC_ACTIVE_INPUTS));
        }
    }

     

     

    I can now define the sample rate freely in RTOS, as long as the sleep time exceeds 81 ms (I sample 3 of the four ADCs, and idle 9s in each sample.

    Of course that is not precise (each line of code takes its time, in particular the two I2C communications). But good enough.

     

    I've set it to 100 ms (from 1 second in the past):

    void *threadADC(void *arg0);
    #define THREAD_PRIORITY_ADC 10
    // sleep has to be higher than 81000 (81 ms idled when sampling 3 ADCs
    // 100 ms : 100000 - 81000
    #define THREAD_USLEEP_ADC 19000

     

    Now testing with all code written and cleaed up ...

    • Cancel
    • Vote Up +1 Vote Down
    • Sign in to reply
    • More
    • Cancel
Comment
  • Jan Cumps
    Jan Cumps over 5 years ago in reply to Jan Cumps

    I have the solution.

    I compared Adafruit's init settings with mine, and the only difference is that they have the device operating mode to one shot, and I to continuous.

    When I set  one-shot too, a reliable sample, including switching the channel, can be retrieved at 9 ms.

     

    image

    Excellent news.

     

    image

    image

     

    The channel configuration is now (last bit from 0 to 1):

    // single conversion mode, +- 6.144V
    #define ADS1115_CFG_H0 0b11000001
    #define ADS1115_CFG_H1 0b11010001
    #define ADS1115_CFG_H2 0b11100001
    #define ADS1115_CFG_H3 0b11110001

     

     

    I need the 9 ms in the sample function, so I defined that fixed:

     

    #define ADS1115_MUX_DELAY 9000
    // ...

     

    In the sample function, the delay is put between MUXIN and reading the result:

     

    uint16_t sampleADC(uint32_t uModule) {
        uint16_t uRetval = 0u;
    
        /* Init ADC and Start Sampling */
        a_txBuffer[1] = array_ADS1115_CFG_H[uModule];
    
        a_txBuffer[0] = 0x01;
        a_i2cTransaction.writeCount = 3;
        a_i2cTransaction.readCount = 0;
        if (! I2C_transfer(i2c_implGetHandle(), &a_i2cTransaction)){
          //        System_printf("Sampling Start Failed \n");
        }
    
        // inspiration: https://github.com/adafruit/Adafruit_ADS1X15/blob/master/Adafruit_ADS1015.cpp
        usleep(ADS1115_MUX_DELAY);
    
        /* Read ADC */
        a_txBuffer[0] = 0x00;
        a_i2cTransaction.writeCount = 1;
        a_i2cTransaction.readCount = 2;
        if (I2C_transfer(i2c_implGetHandle(), &a_i2cTransaction)) {
            uRetval = ((a_rxBuffer[0] << 8) | a_rxBuffer[1]);
        }     else {
    //        System_printf("ADC Read I2C Bus fault\n");
        }
    
        return uRetval;
    }

     

    In the RTOS job, I deduct the time idled in the sample function :

     

    void *threadADC(void *arg0) {
        uint32_t i;
    
        a_i2cTransaction.writeBuf = a_txBuffer;
        a_i2cTransaction.readBuf = a_rxBuffer;
        a_i2cTransaction.slaveAddress = ADC_I2C_ADDR;
    
        a_txBuffer[2] = ADS1115_CFG_L;
    
        while (1)
        {
            for (i =0; i< ADC_ACTIVE_INPUTS; i++) {
                // we write value to the inactive robin
                // store value of ADC[i]
                adcRoundRobin[adcRoundRobinIndex[i] ? 0 : 1].raw[i] = sampleADC(i);
                // after value(s) written, we activate the inactive robin
                adcRoundRobinIndex[i] = adcRoundRobinIndex[i] ? 0 : 1;
            }
    
            // the thread delays ADS1115_MUX_DELAY per sample
            // so let's deduct the total delay from the requested sleep time
            usleep(THREAD_USLEEP_ADC - (ADS1115_MUX_DELAY * ADC_ACTIVE_INPUTS));
        }
    }

     

     

    I can now define the sample rate freely in RTOS, as long as the sleep time exceeds 81 ms (I sample 3 of the four ADCs, and idle 9s in each sample.

    Of course that is not precise (each line of code takes its time, in particular the two I2C communications). But good enough.

     

    I've set it to 100 ms (from 1 second in the past):

    void *threadADC(void *arg0);
    #define THREAD_PRIORITY_ADC 10
    // sleep has to be higher than 81000 (81 ms idled when sampling 3 ADCs
    // 100 ms : 100000 - 81000
    #define THREAD_USLEEP_ADC 19000

     

    Now testing with all code written and cleaed up ...

    • Cancel
    • Vote Up +1 Vote Down
    • Sign in to reply
    • More
    • Cancel
Children
No Data
element14 Community

element14 is the first online community specifically for engineers. Connect with your peers and get expert answers to your questions.

  • Members
  • Learn
  • Technologies
  • Challenges & Projects
  • Products
  • Store
  • About Us
  • Feedback & Support
  • FAQs
  • Terms of Use
  • Privacy Policy
  • Legal and Copyright Notices
  • Sitemap
  • Cookies

An Avnet Company © 2025 Premier Farnell Limited. All Rights Reserved.

Premier Farnell Ltd, registered in England and Wales (no 00876412), registered office: Farnell House, Forge Lane, Leeds LS12 2NE.

ICP 备案号 10220084.

Follow element14

  • X
  • Facebook
  • linkedin
  • YouTube