<?xml version="1.0" encoding="UTF-8" ?>
<?xml-stylesheet type="text/xsl" href="https://community.element14.com/cfs-file/__key/system/syndication/rss.xsl" media="screen"?><rss version="2.0" xmlns:dc="http://purl.org/dc/elements/1.1/"><channel><title>Programmable Electronic Load - ADC Firmware</title><link>https://community.element14.com/technologies/test-and-measurement/w/documents/4855/programmable-electronic-load---adc-firmware</link><description /><dc:language>en-US</dc:language><generator>Telligent Community 12</generator><item><title>Programmable Electronic Load - ADC Firmware</title><link>https://community.element14.com/technologies/test-and-measurement/w/documents/4855/programmable-electronic-load---adc-firmware</link><pubDate>Wed, 06 Oct 2021 22:20:55 GMT</pubDate><guid isPermaLink="false">93d5dcb4-84c2-446f-b2cb-99731719e767:8651e71a-1d75-46ed-8230-ad86a64f7f7c</guid><dc:creator>Jan Cumps</dc:creator><comments>https://community.element14.com/technologies/test-and-measurement/w/documents/4855/programmable-electronic-load---adc-firmware#comments</comments><description>Current Revision posted to Documents by Jan Cumps on 10/6/2021 10:20:55 PM&lt;br /&gt;
&lt;table border="1" class="jiveBorder mce-item-table" height="550" style="border:1px solid #c6c6c6;height:535px;width:846px;"&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td style="border:1px solid black;border:1px solid #c6c6c6;background-color:#ffffdb;padding:6px;"&gt;&lt;p style="margin:0;"&gt;This post documents the ADC firmware for the &lt;a class="jive-link-wiki-small" href="/technologies/test-and-measurement/w/documents/1896/programmable-electronic-load"&gt;electronic load we made here on element14&lt;/a&gt;.&lt;/p&gt;&lt;table border="0px" class="jiveBorder mce-item-table" height="694" style="border:0px solid #c6c6c6;width:389px;height:429px;" width="621"&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td style="border:0pxpx solid black;border:0px solid #c6c6c6;background-color:#ffffdb;vertical-align:middle;text-align:center;padding:6px;"&gt;&lt;p style="margin:0;"&gt;&lt;span&gt;&lt;a href="https://community.element14.com/resized-image/__size/800x480/__key/communityserver-wikis-components-files/00-00-00-00-21/contentimage_5F00_135325.png"&gt;&lt;img alt="image" src="https://community-storage.element14.com/communityserver-components-secureimagefileviewer/communityserver/wikis/components/files/00/00/00/00/21/contentimage_135325.png-800x480.png?sv=2016-05-31&amp;amp;sr=b&amp;amp;sig=bqU%2FUswAEK3mAf5Dx2v3pcmfeEAOiWet1%2FEXI%2F%2FrrMA%3D&amp;amp;se=2026-04-27T23%3A59%3A59Z&amp;amp;sp=r&amp;amp;_=vFwbx8DVgGwtMFPEM8AyCA==" style="max-height: 480px;max-width: 800px;" /&gt;&lt;/a&gt;&lt;/span&gt;&lt;/p&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;&lt;p style="margin:0;padding:0px;"&gt;&amp;nbsp;&lt;/p&gt;&lt;h5&gt;ADC&lt;/h5&gt;&lt;p style="margin:0;padding:0px;"&gt;&amp;nbsp;&lt;/p&gt;&lt;p style="margin:0;"&gt;&lt;span&gt;&lt;a href="https://community.element14.com/resized-image/__size/327x185/__key/communityserver-wikis-components-files/00-00-00-00-21/contentimage_5F00_135326.png"&gt;&lt;img loading="lazy" alt="image" src="https://community-storage.element14.com/communityserver-components-secureimagefileviewer/communityserver/wikis/components/files/00/00/00/00/21/contentimage_135326.png-327x185.png?sv=2016-05-31&amp;amp;sr=b&amp;amp;sig=CIirivGiqerz6GT2%2FQQ0I%2B6Xfws720l5LbRZ%2ByjMiAc%3D&amp;amp;se=2026-04-27T23%3A59%3A59Z&amp;amp;sp=r&amp;amp;_=1Yghan/bXhIGcI6uOnUBiw==" style="max-height: 185px;max-width: 327px;" /&gt;&lt;/a&gt;&lt;/span&gt;&lt;/p&gt;&lt;p style="margin:0;padding:0px;"&gt;&amp;nbsp;&lt;/p&gt;&lt;p style="margin:0;"&gt;ADC samples are taken all the time, based on a TI-RTOS schedule.&lt;/p&gt;&lt;p style="margin:0;"&gt;Data is written to a round-robin buffer with 2 buckets.&lt;/p&gt;&lt;p style="margin:0;padding:0px;"&gt;&amp;nbsp;&lt;/p&gt;&lt;p&gt;&lt;pre class="ui-code" data-mode="c_cpp"&gt;volatile ADCValues adcRoundRobin[2];
volatile uint32_t adcRoundRobinIndex[ADC_ACTIVE_INPUTS] = {0};&lt;/pre&gt;&lt;/p&gt;&lt;div style="display:none;"&gt;&lt;/div&gt;&lt;p style="margin:0;padding:0px;"&gt;&amp;nbsp;&lt;/p&gt;&lt;p style="margin:0;"&gt;One bucket has stable data and can be read whenever needed. A read pointer points to that stable bucket.&lt;/p&gt;&lt;p style="margin:0;"&gt;The other bucket is used to write sample data. &lt;span style="text-decoration:line-through;"&gt;Once samples from 4 channels are collected, the read pointer is toggled so that it points to that fresh data.&lt;/span&gt;&lt;/p&gt;&lt;p style="margin:0;"&gt;I&amp;#39;ve optimised this. I&amp;#39;ve added a read pointer for each channel now:&lt;/p&gt;&lt;p style="margin:0;padding:0px;"&gt;&amp;nbsp;&lt;/p&gt;&lt;p&gt;&lt;pre class="ui-code" data-mode="c_cpp"&gt;volatile uint32_t adcRoundRobinIndex[ADC_ACTIVE_INPUTS] = {0};&lt;/pre&gt;&lt;/p&gt;&lt;div style="display:none;"&gt;&lt;/div&gt;&lt;p style="margin:0;padding:0px;"&gt;&amp;nbsp;&lt;/p&gt;&lt;p style="margin:0;"&gt;Once a channel is collected, the read pointer for that channel is toggled so that it points to the fresh data.&lt;/p&gt;&lt;p style="margin:0;padding:0px;"&gt;&amp;nbsp;&lt;/p&gt;&lt;p style="margin:0;padding:0px;"&gt;&amp;nbsp;&lt;/p&gt;&lt;p&gt;&lt;pre class="ui-code" data-mode="c_cpp"&gt;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&amp;#39;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&amp;lt; 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;
        }

    }
}&lt;/pre&gt;&lt;/p&gt;&lt;div style="display:none;"&gt;&lt;/div&gt;&lt;p style="margin:0;padding:0px;"&gt;&amp;nbsp;&lt;/p&gt;&lt;p style="margin:0;"&gt;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.&lt;/p&gt;&lt;p style="margin:0;padding:0px;"&gt;&amp;nbsp;&lt;/p&gt;&lt;p&gt;&lt;pre class="ui-code" data-mode="c_cpp"&gt;x = sampleADC(i, (UInt)arg0/4);&lt;/pre&gt;&lt;/p&gt;&lt;div style="display:none;"&gt;&lt;/div&gt;&lt;p style="margin:0;padding:0px;"&gt;&amp;nbsp;&lt;/p&gt;&lt;p style="margin:0;"&gt;Sampling is done via I²C. First part is switching the ADC channel. Then a sleep to give the ADC time, then fetch:&lt;/p&gt;&lt;p style="margin:0;padding:0px;"&gt;&amp;nbsp;&lt;/p&gt;&lt;p&gt;&lt;pre class="ui-code" data-mode="c_cpp"&gt;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(), &amp;amp;a_i2cTransaction)){
        System_printf(&amp;quot;Sampling Start Failed \n&amp;quot;);
    }

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

    // ...

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

    return uRetval;
}&lt;/pre&gt;&lt;/p&gt;&lt;div style="display:none;"&gt;&lt;/div&gt;&lt;p style="margin:0;padding:0px;"&gt;&amp;nbsp;&lt;/p&gt;&lt;p style="margin:0;"&gt;Reading is done via a helper function - exposed as API to the program. This can be used anytime without locking or semaphore, because &lt;span style="text-decoration:line-through;"&gt;the time between&lt;/span&gt; switching the read pointer&lt;span style="text-decoration:line-through;"&gt; and the next possible write in that buffer is way longer (several orders of magnitude) than calling the helper function&lt;/span&gt; is an atomic command.&lt;/p&gt;&lt;p style="margin:0;padding:0px;"&gt;&amp;nbsp;&lt;/p&gt;&lt;p&gt;&lt;pre class="ui-code" data-mode="c_cpp"&gt;uint16_t adcImplGetAdc(uint32_t uModule) {
    return adcRoundRobin[adcRoundRobinIndex[uModule]].raw[uModule];
}&lt;/pre&gt;&lt;/p&gt;&lt;div style="display:none;"&gt;&lt;/div&gt;&lt;div style="clear:both;"&gt;&lt;/div&gt;
</description></item></channel></rss>