This post documents the ADC firmware for the electronic load we made here on element14. |
ADC
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]; }
Top Comments