Closed BenjaminPelletier closed 2 months ago
Wow, thank you for the fantastic explanation!
Coincidentally, I have a follower on Instagram who noticed the same erratic behaviour and reported it to me very recently. At high acquisition speeds, he got the exact same symptoms as you presented here. I tried to reproduce it, but sometimes it occurred, sometimes not, so I just thought it might be poor wiring or floating inputs. I was wrong.
It is interesting that this issue happens because there should be a firm handshake signal when the new acquisition can start which is the DRDY pin's status change. I carefully followed the datasheet of the chip and double-checked the timings with a logic analyser.
So, coming back to the code you presented, it seems that I need to introduce an additional delay between two readings while cycling the differential inputs. This is where the handshake (DRDY) pin should drive the subsequent readings, but seemingly it does not do it. The code is waiting before switching the MUX to the new channel and when the DRDY goes low, it lets the acquisition run. Then it waits again for the DRDY... and so on... With your modification, it seems that you added a little extra time after the acquisition is done and before the code starts to wait for the DRDY before changing the MUX to the new channel.
I put a delayMicroseconds(120); line directly before the waitForDRDY(); in the cycleDifferential() function and now it seems that the values are stable.
Do you think that I should do it differently, or I could just insert this small delay directly in the cycleDifferential() function?
It does look like you are very meticulous (more so than me; I think we're both good stereotypes of engineer and scientist) so I am looking forward to seeing why this happens. I'm planning to look into it too, but wanted to document the repro steps before moving onto that :)
I think the 120 us delay should practically work, but I feel like that's just the empirical solution and I'm interested in knowing why that's needed (plus, I'd expect adding a delay like that to decrease the achieved sample rate and that's very undesirable). Like you say, I would expect the chip to work exactly like the datasheet says, so I wouldn't expect that additional delay to be necessary if the implementation were perfect. If it is necessary despite a perfect implementation, then the next bug report would probably be to Texas Instruments, but I don't know that I've ever experienced a hardware bug myself before.
Thanks for the library, and especially the excellent documentation :)
Thank you!
Well, based on the details you provided in your comments, I think you are even more meticulous than me. I am really thankful that you took the time to try to work on the solution and provide a very good step-by-step guide!
I have the same feeling about adding a manual delay. It feels very empirical. I am not a big fan of "magic numbers" because they are usually just a temporary fix, but they don't solve the root cause of the issue.
I will run a few more tests with a logic analyser and oscilloscope to be 100% sure, but I already did it so many times when I wrote the library. But it is of course possible that I overlooked something. If it still does not lead to anything, then I will contact TI and ask them about their opinion.
I am very happy to hear that you liked the documentation. It saves a lot of time in the long run because I can just tell people to read it. I think I documented most of the things down to the tiniest details.
So, I could not resist and quickly hooked up a test system with a logic analyser. I monitored the DRDY, SCLK, DIN (MOSI) and DOUT (MISO) pins.
The signals below are captured from the code that has no delay. If you see the DRDY signals, it is rather random. It "misfires" several times and this seems to cause the erroneous behaviour.
If you look at this image below, you can see that it is much more homogeneous and the DRDY has a very periodic signal. This signal is produced with the 120 us delay before the waitForDRDY() function. This seems to make everything correct.
Now I need to figure out if it is my code or the ADS1256!?
That's fantastically useful. I'll see if I can reproduce with an oscilloscope to see if it might be an electrical noise thing versus a digital logic thing (presumably the latter, but perhaps worth ruling things out)
Hmm, at first glance, it seems like the ADS1256 is only being queried twice for a cycle of "four" channel measurements. Below, red is SCLK and blue is DRDY. I'm running a 4-channel read cycle every second, and there does not appear to be any SCLK movement outside this period. The sketch I'm using is below as well.
...and this is after setting the ADDITIONAL_DELAY_US constant to 120:
It looks like the library is behaving as if DRDY went low when it did not go low electrically. Here's a zoomed in view, back to no additional delay (current bad behavior):
Continuing the investigation :)
Trial sketch:
#include <ADS1256.h>
const uint8_t ADC_DRDY = 2;
const uint8_t ADC_RESET = 0;
const uint8_t ADC_PDWN = 8;
const uint8_t ADC_CS = 10;
const float ADC_VREF = 5.0f;
ADS1256 adc(ADC_DRDY, ADC_RESET, ADC_PDWN, ADC_CS, ADC_VREF);
const uint8_t N_CHANNELS = 4;
const uint16_t ADDITIONAL_DELAY_US = 0;
void setup()
{
Serial.begin(115200);
while (!Serial)
{
; //Wait until the serial becomes available
}
Serial.println("Initializing ADC1256...");
adc.InitializeADC();
Serial.println("Initialized.");
adc.setPGA(PGA_1);
adc.setDRATE(DRATE_30000SPS);
Serial.println("Configured.");
delay(1000);
}
void loop()
{
const int32_t THRESHOLD = 100000;
int32_t value1, value2, value3, value4;
uint16_t n_total = 0;
uint16_t n_bad = 0;
for (uint16_t i = 0; i < 1; i++) {
value1 = adc.cycleDifferential();
delayMicroseconds(ADDITIONAL_DELAY_US);
value2 = adc.cycleDifferential();
delayMicroseconds(ADDITIONAL_DELAY_US);
value3 = adc.cycleDifferential();
delayMicroseconds(ADDITIONAL_DELAY_US);
value4 = adc.cycleDifferential();
delayMicroseconds(ADDITIONAL_DELAY_US);
n_total++;
if (value1 < THRESHOLD || value2 > THRESHOLD || value3 > THRESHOLD || value4 > THRESHOLD) {
n_bad++;
}
}
Serial.print(n_bad);
Serial.print("\tbad out of ");
Serial.print(n_total);
Serial.print("; last measurement:\t");
Serial.print(value1);
Serial.print("\t");
Serial.print(value2);
Serial.print("\t");
Serial.print(value3);
Serial.print("\t");
Serial.print(value4);
Serial.println();
delay(1000);
}
Hmm. After rereading the datasheet, I might have some clue about the issue. It would explain at least the fact that the erroneous behaviour is only present at high sampling speeds.
On page 20, table 13 mentions the settling time. And on page 21, table 14 mentions the MUX cycling throughput.
It somehow feels that this could be the key, however, I might misinterpret it. At high sampling speeds, the conversion (basically, 1 cycle) is performed so fast that when the code is about to reach the MUX changing part of the code again, the settling time has not yet passed. And then this can cause the second channel to return the first channel's value.
I zoomed in the chart here and there, and I found parts where the time from WREG to WREG (t19) is less than 228.6 us which is t19 at 30 ksps. Also, the DRDY is still high when the next WREG is written, although this should not happen.
I believe that adding the extra delay helps both the MUX to settle and the DRDY to properly settle at the desired status.
On the other hand, as you also mentioned, it messes up the sampling frequency. The throughput goes down to roughly 1.8 kHz, instead of 4.3 kHz. After a little sleep, I will try to dig deeper.
I think the DRDY handshake is being misinterpreted. Below is what I think is a zoomed-in trace of two calls to cycleDifferential
with my annotations:
I believe the primary problem is the bold text. At this point, DRDY falls and therefore marks DRDY_value as true, but then DRDY is, in fact, not ready at the time waitForDRDY() is later called. The solution should be to set DRDY_value to false just before the first possible moment it could potentially indicate true readiness for the next call. I have a draft PR that I think does this close to correctly, but I think you may need to refine the locations I've set DRDY_value to false.
Attempting to
cycleDifferential
at high speed results in incorrect values. Specifically, the measurement from the differential pair is repeated as the value for the second differential pair, and the same value (I'm not sure which) is repeated for both the third and fourth differential pairs.To reproduce, use the first sketch below. Physically tie each AINx to ground, except tie AIN0 to 3.3V. I used an Arduino Uno. Here is a sample of the output I observed:
Each line is printing the 4 differential values from a full cycle of
cycleDifferential
. Note:I strongly suspect this is due to incorrect timing or handshakes in the acquisition of differential measurements from the ADS1256. The difference between the two lines is that "Print while reading" writes content to the serial port after each channel's measurement while "Print after reading" waits until all 4 channels' measurements have been captured before writing to the serial port. This means there is an additional delay in "Print while reading" that is not present in "Print after reading". Since the latter fails while the former succeeds, I suspect that a manual delay is not set to be long enough in the library, or else an handshake signal is interpreted incorrectly.
If I use the second sketch below to figure out what additional delay fixes the issue, the answer appears to be "at least 112 microseconds":
Demo sketch:
Additional delay evaluation sketch: