Maybe this helps to set up the differential ADC in principle. https://forum.pjrc.com/threads/57890...dout-with-T3-2
Maybe this helps to set up the differential ADC in principle. https://forum.pjrc.com/threads/57890...dout-with-T3-2
Unfortunately, your circuit diagram illustrates a major problem with direct connection of Wheatstone bridges. They often present several K Ohms of impedance at the ADC inputs. This can slow the response of the circuit significantly. Depending on the connections, those high-impedance inputs can also pick up noise from nearby digital circuits. Differential sampling will help with that, but may not fully eliminate the noise. Another problem with these circuits is that the output voltage differential may be just a few millivolts.
This circuit really needs a fast differential amplifier with high-impedance inputs to convert the signal into a single-ended output. We always needed something like that on our oceanographic pressure sensors and we were only sampling at about 400Hz.
If you could post the type of sensor you are using, I might be able to give you an amplifier design that could clean up the signal and require only a single ADC input--which could easily be sampled at 1MHZ using code posted earlier.
Hello mborgerson, thanks that would be amazing (in the meantime I will start looking into this myself, I was not aware that I could use an amplifier to get a single output...yes my electronics noob status is still firmly intact)! I am using the Endevco 8530C-100 pressure sensor, datasheet link below:
https://buy.endevco.com/ContentStore..._DS_082219.pdf
Can I really push my luck and also ask, how did you convert the .DAT file created by the code in post #45 to a readable format? I use matlab to convert the data from code in post #26 to a readable format but have not been able to do the same for the code in post #45.
For a strain-guage wheatstone bridge configuration you need an instrumentation amp to boost the differential mode signal.
There are many instrumentation amps you can get, its basically 3 opamps in a single package.
https://en.wikipedia.org/wiki/Instrumentation_amplifier
https://www.ti.com/amplifier-circuit.../overview.html
The output data from the 1MSample logger is a binary file with a sequence of 16-bit unsigned integers. Each integer can have a value between 0 and 4095, which represents the range of values output by the ADC in 12-bit mode. Here is a simple Matlab function to read and display the data on the SD Card.
Code:function [dout]=load1MSample(filnam) % load analog logger collection of 1MSample data with ADC Timer % 1/22/21 MJB if nargin == 0 [raw_name,temp,filterindex]=uigetfile('*.dat','Load 1MSample Logger File'); filnam=[temp raw_name]; else filnam = [filnam]; end disp(sprintf('Reading data from %s',raw_name)); fid = fopen(filnam,'r'); fseek(fid,0,'eof'); % move to end of file pos2 = ftell(fid); % pos2 is overall length of file frewind(fid); % move back to beginning of file % each record is 2 bytes % uint16 adc A9 value nrecords=floor((pos2)/2); % number of records disp(sprintf('%d records of data in the file',nrecords)); fseek(fid,0,'bof'); % next read ADC adcref = 3.32; % assumed value of adc reference % in 12-bit mode max output count is 4095 adcounts = fread(fid,nrecords,'uint16'); dout = adcounts * (adcref/4095.0); fclose(fid); plot(dout); end
I assume that you are driving the sensor with 5V and ground for your power. The outputs should be at about 2.5V each. The T4.1 ADC has an input limit of about 3.3Volts, so you probably need a design that offsets the output signal down closer to zero volts. Otherwise pressure signals over 0.8 Volts will saturate your ADC inputs. You need to go through your sensor's sensitivity specs and calculate the maximum signal you expect in your tests, then design the circuit so that the output will be within the 0 to 3.3V range of the ADC. You also need to make sure that the amplifier has the bandwidth and slew rate to handle the fast pressure transients. (If the transients were slow, there would be no need to sample at 1MHz!)
I used your code to graph my full-wave rectified AC signal to see if I could get a clean waveform, but I just got three peaks in the first 20 samples, but then I got a decent half-wave with the next 18 samples, followed by six more peaks with the last 20 samples.
Sorry I lack the intuition to know what's going on here.
Here's the data,
I changed ReadADC(void) so ADCChore would start at 200 uS intervals, i.e.,Code:222 170 82 249 162 77 5 3 48 120 186 248 48 112 183 2 72 135 195 246 38 82 119 152 184 204 214 223 230 234 237 238 238 236 228 201 154 88 10 195 120 46 222 137 51 219 132 48 20 25 75 146 208 11 70 134 209 26 92 157
ADCTimer.begin(ADCChore,200.0);
and I cut the samples down to 60 and reduced the delay to 100 mS, i.e., delay(100).
So, I'm wondering why "void ADCChore(void)" would have a hard time starting up and finishing, but do so well in the middle?
At 60 Hz for the input signal, I thought this would be a cakewalk. In fact, I found your sample code because doing a simple analogRead() was taking 100 to 2000 mS per sample which is quite useless when I need at least 17 samples for the 16.7 mS period of a 60 Hz signal.
This is a lot of fun, but I am overwhelmed at the moment.
Thank you for providing the sample code and I hope you can help me out on this quest.
If you'll post your code, I'll look it over. I haven't run the original code in a year or more, so I can't really tell you much without looking at your code.
Thank you! I was working on a webserver with analog inputs but deleted most of the webserver code to simplify the debugging.
Code:/* WebLogger.ino */ #include <SPI.h> #include <DateTime.h> #include <NativeEthernet.h> // #include <EthernetUdp.h> #include <DateTimeStrings.h> #include <TimeLib.h> #include <ADC.h> #define TIME_MSG_LEN 11 // timesync to PC is HEADER followed by unix time_t as 10 digits #define TIME_HEADER 255 // Header tag for serial time sync message #define BUFFERSIZE 20 // Only expect 20 samples per cycle #define ADCMAX 1024 // for 10-bit samples float vRef = 3.298; // typical for my T4 float pk_pkSamples[1000]; uint16_t numsamples; int bufferV[BUFFERSIZE]; // Voltage int bufferT[BUFFERSIZE]; // Time in mS int valueV, valueT, samples;// For immediate comparisons long startTime; long stopTime; long totalTime; bool noEthernet=true; // Important to know if there's no ethernet bool quiet=false; bool quietHTMLresponse=false; bool StsI=false; // Internet offline bool StsP=false; // Power Outage bool NTPR=false; // NTP Reponse bool nnl=false; // Need \n - It's true if I've printed a '+' or '-' char buf[100]; byte Second,Minute,Hour,Day,DayofWeek,Month, Year; // note year is actual year minus 2000 (ie 9 is 2009) // Enter the IP address for your controller below. // I am going to use the default MAC from the Teensy, but I found a free IP and used that. byte mac[6]; EthernetUDP Udp; unsigned int localPort = 8888; // local port to listen for UDP packets IPAddress ip(192, 168, 1, 101); const int timeZone = -5; // Eastern Standard Time (USA) // NTP Servers: pool.ntp.org IPAddress timeServer(45,79,1,70); // clover0.mattnordhoff.net // I wanted to use Spectrum, but I had trouble with all the aliases char google[] = "google.com"; // Initialize the Ethernet server library // with the IP address and port you want to use EthernetServer server(80); // default port EthernetClient TestClient; // The internet client I'll be pinging // NTP code const int NTP_PACKET_SIZE = 48; // NTP time is in the first 48 bytes of message byte packetBuffer[NTP_PACKET_SIZE]; //buffer to hold incoming & outgoing packets void sendNTPpacket(IPAddress &address); // Declare before use void teensyMAC(byte mac[]) { for(uint8_t by=0; by<2; by++) mac[by]=(HW_OCOTP_MAC1 >> ((1-by)*8)) & 0xFF; for(uint8_t by=0; by<4; by++) mac[by+2]=(HW_OCOTP_MAC0 >> ((3-by)*8)) & 0xFF; Serial.printf("MAC: %02x:%02x:%02x:%02x:%02x:%02x\n", mac[0], mac[1], mac[2], mac[3], mac[4], mac[5]); } time_t getNtpTime() { while (Udp.parsePacket() > 0) ; // discard any previously received packets if(nnl){ Serial.println(""); nnl=false; } Serial.println("Transmit NTP Request"); sendNTPpacket(timeServer); uint32_t beginWait = millis(); while (millis() - beginWait < 1500) { int size = Udp.parsePacket(); if (size >= NTP_PACKET_SIZE) { Serial.println("Receive NTP Response"); Udp.read(packetBuffer, NTP_PACKET_SIZE); // read packet into the buffer unsigned long secsSince1900; // convert four bytes starting at location 40 to a long integer secsSince1900 = (unsigned long)packetBuffer[40] << 24; secsSince1900 |= (unsigned long)packetBuffer[41] << 16; secsSince1900 |= (unsigned long)packetBuffer[42] << 8; secsSince1900 |= (unsigned long)packetBuffer[43]; NTPR=true; return secsSince1900 - 2208988800UL + timeZone * SECS_PER_HOUR; } } if(nnl){ Serial.println(""); nnl=false; } Serial.println("No NTP Response :-("); NTPR=false; return 0; // return 0 if unable to get the time } // YYMMDD HH:MM:SS. Take the year modulo 100 to get the decade int currentDateTime(){ return(sprintf(buf, "%02d%02d%02d %02d:%02d:%02d", year()%100, month(), day(), hour(), minute(), second())); } int currentTime(){ return(sprintf(buf, "%02d:%02d:%02d", hour(), minute(), second())); } volatile bool sampling = false; volatile uint16_t adcmax, adcmin; IntervalTimer ADCTimer; ADC *adc = new ADC(); // adc object #define ADMAX 60 uint8_t adcbuffer[ADMAX]; volatile uint32_t adcidx; /***************************************************** * This is the intervaltimer interrupt handler ******************************************************/ void ADCChore(void){ uint8_t adval; if(adcidx < ADMAX){ // sample until end of buffer adc->adc0->startSingleRead(A0); // start a single conversion adcbuffer[adcidx] = adc->adc0->readSingle(); adcidx++; } } /****************************************************** Read MAXSAMPLES from ADC at 1 microsecond intervals Store the results in adcbuffer; *****************************************************/ void ReadADC(void) { Serial.println("\nReading ADC Samples"); adcidx = 0; ADCTimer.begin(ADCChore,200.0); // start timer at 200 uS intervals delay(100); // Should suffice to read the 16.7mS cycle ADCTimer.end(); // stop the timer Serial.print("ADC Read "); Serial.print(adcidx); Serial.println(" samples"); } /****************************************************** * Display data from adcbuffer in lines of 20 values * Only the first NUMTOSHOW values are displayed *****************************************************/ #define NUMTOSHOW 60 // Just the 34 samples void ShowADC(void) { uint32_t sendidx; Serial.println("ADC Data"); for (sendidx = 0; sendidx < NUMTOSHOW; sendidx++) { Serial.printf("% 4u", adcbuffer[sendidx]); } } void setup() { pinMode(A0, INPUT_DISABLE); adc->adc0->setAveraging(1 ); // set number of averages adc->adc0->setConversionSpeed(ADC_CONVERSION_SPEED::VERY_HIGH_SPEED); // change conversion speed adc->adc0->setSamplingSpeed(ADC_SAMPLING_SPEED::VERY_HIGH_SPEED); // change sampling speed pinMode(A0, INPUT); // Line Power signal from unfiltered 6V A/C rectified PS // Open serial communications and wait for port to open: Serial.begin(115200); while (!Serial) { ; // wait for serial port to connect. Needed for native USB port only } Serial.println("Power & Internet Monitor"); teensyMAC(mac); // Get the address Serial.printf("Using MAC: %02x:%02x:%02x:%02x:%02x:%02x\n", mac[0], mac[1], mac[2], mac[3], mac[4], mac[5]); // start the Ethernet connection and the server: Ethernet.begin(mac, ip); // Check for Ethernet hardware present if (Ethernet.hardwareStatus() == EthernetNoHardware) { Serial.println("Ethernet shield was not found. Sorry, can't run browser without hardware. :("); // Since we can still monitor power, just log this noEthernet=true; } if (Ethernet.linkStatus() == LinkOFF) { Serial.println("Ethernet cable is not connected."); } if(!noEthernet){ // start the server server.begin(); Serial.print("Web server is at "); Serial.println(Ethernet.localIP()); Udp.begin(localPort); Serial.println("waiting for sync"); setSyncProvider(getNtpTime); } } int prevDay = 0; // when the last day was displayed #define MaxLineLen 100 int nlcounter=0; // Count chars so we can do a newline unsigned long previousMillis = 0; unsigned long previous10sCounter = 0; // Loop interval time v delay() unsigned long currentMillis=1000; // Force loop interval the first time through unsigned long current10sCounter=0; // Skip the first time through bool WaitRetry=true; // Wait a cycle bool ImIdle=true; // Idle flag elapsedMicros lastLPsample; // Line Power. Initial imaginary sample elapsedMillis lastLPcyle; // Line Power - sample every 5 seconds int i; // Line Power Samples Count void loop() { ReadADC(); ShowADC(); } /*-------- NTP code ----------*/ // send an NTP request to the time server at the given address void sendNTPpacket(IPAddress &address) { // set all bytes in the buffer to 0 memset(packetBuffer, 0, NTP_PACKET_SIZE); // Initialize values needed to form NTP request // (see URL above for details on the packets) packetBuffer[0] = 0b11100011; // LI, Version, Mode packetBuffer[1] = 0; // Stratum, or type of clock packetBuffer[2] = 6; // Polling Interval packetBuffer[3] = 0xEC; // Peer Clock Precision // 8 bytes of zero for Root Delay & Root Dispersion packetBuffer[12] = 49; packetBuffer[13] = 0x4E; packetBuffer[14] = 49; packetBuffer[15] = 52; // all NTP fields have been given values, now // you can send a packet requesting a timestamp: Udp.beginPacket(address, 123); //NTP requests are to port 123 Udp.write(packetBuffer, NTP_PACKET_SIZE); Udp.endPacket(); }
A few notes:
1. in Setup you have:
pinMode(A0, INPUT_DISABLE);
adc->adc0->setAveraging(1 ); // set number of averages
adc->adc0->setConversionSpeed(ADC_CONVERSION_SPEED::VERY_HIG H_SPEED); // change conversion speed
adc->adc0->setSamplingSpeed(ADC_SAMPLING_SPEED::VERY_HIGH_SP EED); // change sampling speed
pinMode(A0, INPUT); // Line Power signal from unfiltered 6V A/C rectified PS
pinMode(A0, INPUT_DISABLE); is correct for ADC input.
A few lines later you have: pinMode(A0, INPUT);
This is not good for analog input as it enables some internal resistors which can affect the analog signal.
2. The use of VERY_HIGH_SPEED is not necessary at your 5KHz sample rate. HIGH_SPEED is probably fine and might give better results.
3. You are saving the ADC values as uint8_t, but you never set the resolution to 8 bits. I think the default resolution is 10 bits, so you're going to end up with weird values whenever the result is greater than 255.. You need to add
adc->adc0->setResolution(8); // set bits of resolution
to your setup, or change your adc buffer to uint16_t and adjust your processing accordingly.
4. I haven't used ethernet on the Teensy, so I'm not sure whether it might interfere with the ADC timer. In my faster loggers I often increase the priority of the ADC timer interrupt with :
ADCTimer.priority(50); // medium-high priority
I think it is normally 128 like many other systems (higher numbers mean lower priority).
5. In the timer handler, the code stores the result of the previous conversion AFTER starting the next conversion. This reduces the duration of the interrupt handler and was helpful at very high conversion rates. This depends on the fact that a new value is not yet available when readSingle() is called. It also means that the first value in the array is whatever was left from the last conversion of the previous collection.
You can fix this by doing a conversion just before starting the adc timer:
adc->adc0->startSingleRead(A0); // call this to setup everything before the Timer starts
adcidx = 0;
ADCTimer.begin(ADCChore,200.0); // start timer at 200 uS intervals
6. Just to be safe, I would add a few milliseconds delay between the Serial.println() call and starting the ADC timer. Having a bunch of USB activity at the start of your conversion cycle probably won't mess up the timing at your conversion rate, but it shouldn't hurt to make sure the system has a bit less noise as you do the conversions.
Whoa, Nelly! Thank you so much for your detailed feedback! I did notice some weird values which I'm sure are due to the >256 values and reading under 1V when I expected reading the high values to be over 2V.
Sorry, I instinctively replied to your email v posting here.
I'll fix the errors in my code as you pointed out and post the results if I find anything extraordinary in the results, but with your detailed analysis and recommendations, I'll just say, Kudos to you for sharing your expertise so quickly and freely.
BTW, I got a chance to review some of the code in hardware/teensy/avr/libraries/ADC/ADC.cpp and hardware/teensy/avr/libraries/ADC/ADC_Module.cpp, but I didn't get the clue of casting and using int16_t or setting the resolution to 8-bit.
I implemented the changes Paul Stoffregen suggested and it worked perfectly!
The full-wave rectified sine wave looks just as it does on my oscilloscope!
I didn't really need full-fidelity as my objective was to simply verify it was a 60 Hz signal and get the Peak-Peak voltage to determine my utility line voltage in case of a brown-out or a power outage. Well, I did want to add more fidelity later because I wanted to detect power glitches with over and under voltage alerts, but I thought that be a few months the road after everything is working fine on this project. I.e., I wanted to add interrupts to the other conditions; however, this would require constant monitoring and I don't like the idea of using a controller to keep such a close eye on the powerline. I thought I would use a very basic electronic circuit to detect the aforementioned conditions and simply trigger an interrupt so the controller would read three digital input pins to determine what the condition was that triggered the interrupt. This would allow me to let the controller sleep to conserve energy and just wake up to poll for routine status and wake up in response to interrupts.
Just as a flashback to what Paul pointed out. I thought it was odd that my waveform before would be rising, then get a very low data point then resume its sinusoidal trajectory with more weird points that would be way off course.