1 MSPS on a T4? Is this possible...

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.
 
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/MktgContent/Endevco/Datasheet/8530C_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.
 
I have since Late December 2020 been trying to modify the code in post #26 to accommodate measurement of a full wheatstone bridge (Picture attached), doing this will reduce much of the environmentally induced variability in my readings from a pressure sensor but to put it bluntly it's been an astounding disaster.

View attachment 23347

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/instrumentation/overview.html
 
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.

:eek: I've really been turning over the wrong stones. Thank you MarkT. This is exactly what I needed, just didn't know it.

@mborgerson, maybe I should first put some time into this before you spend more of your time finding a suitable amplifier design to suggest.
 
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.

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,
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
I changed ReadADC(void) so ADCChore would start at 200 uS intervals, i.e.,
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.
 
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_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

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.
 
60 Hz sampling with Teensy 4.1

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.

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_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

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.
 
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.
 
Struggling to get MTP.h

I modified my sample code to use interleaved ADC collection per jonr's sample code and added some timing data collection using the ARM cycle counter. I also tested with the resolution set to 12 bits. With the new setup, the time spent in the intervaltimer service routine is about 220nsec.

I connected the ADC to a signal generator and tested various frequencies from 100Hz to 10Khz and got good results:
View attachment 21329

That is a ~10KHz signal---which takes about 100 samples per cycle.

I found the following points interesting:

1. There can be up to 0.42 microseconds of jitter in the start of the timer interrupt handler. I presume that this is due to other drivers blocking interrupts while handling Serial output and SD Card writing. I Tested without SD writes and Serial output and the max jitter decreased to 0.13uSec. Better--but still not perfect--perhaps due to things in Serial and the system tick.

2. It didn't seem to make any difference whether I put my SD card buffers in regular RAM or DMAMEM.

3. Both EXFat and FAT32 SD cards worked about the same when pre-allocation was used.

Here's the updated code

Code:
/*******************************************************
   One MegaSample T4.0 ADC  12-bit
   MJB   4/2/20
         updated 8/9/2020
   NOTE:  This is a proof-of-concept program written for
          a novice programmer.  In the interest of simplicity
          it uses global variables and simple functions without
          parameters.

          The sampling runs fast enough at 600MHz that ther is
          time for the native SDIO interface to write the data
          to an sd card.

          The program uses the ADC library from Pedvide, which
          is one of the libraries installed by  Teensyduino.
          If you plan to do high-speed ADC acquisition, learning
          how this library works should be high on your TO-DO
          list.


***************************************************************/
#include "SdFat.h"
#include "sdios.h"
#include <TimeLib.h>
#include <ADC.h>

/*******************************************************/
// when USEMTP is defined, you can upload file with MTP,
// but you have to have the MTP library and modified USB files
#define USEMTP   

#ifdef USEMTP
#include "MTP.h"
#include <Storage.h>
#include <usb1_mtp.h>

MTPStorage_SD storage;
MTPD       mtpd(&storage);
#endif
/*******************************************************/

IntervalTimer ADCTimer;

// instantiate a new ADC object
ADC *adc = new ADC(); // adc object;

// use an elapsedmillis object to control collection time
elapsedMillis collectmillis;


// This version uses SdFs, which can be either FAT32 or EXFat
SdFs sdf;
SdioCard sdc;
FsFile logFile;


#define SD_CONFIG SdioConfig(FIFO_SDIO)
const char compileTime [] = "T4.1 MegaSample 12-bit logger Compiled on " __DATE__ " " __TIME__;
const int admarkpin = 32;   // Changed to end pins on T4.1
const int wrmarkpin = 33;
const int ledpin    = 13;

// ADMARKHI and ADMARKLO are used to observe ADC timing on oscilloscope
#define  ADMARKHI digitalWriteFast(admarkpin, HIGH);
#define  ADMARKLO digitalWriteFast(admarkpin, LOW);

// WRMARKHI and WRMARKLO are used to observe SDC Write timing on oscilloscope
#define  WRMARKHI digitalWriteFast(wrmarkpin, HIGH);
#define  WRMARKLO digitalWriteFast(wrmarkpin, LOW);

#define  LEDON digitalWriteFast(ledpin, HIGH);
#define  LEDOFF digitalWriteFast(ledpin, LOW);


#define ADBLOCKSIZE  (1024 * 100)   //  2 x 200KBytes to leave some room for other users of DMAMEM

uint16_t  adcbuff0[ADBLOCKSIZE];
uint16_t  adcbuff1[ADBLOCKSIZE];

volatile uint16_t inbuffnum = 0;
volatile bool logging = false;

uint16_t *inbuffptr, *sdbuffptr;
volatile uint32_t adcidx;
uint32_t totalbytes;

void setup() {
  // put your setup code here, to run once:
  Serial.begin(9600);
  delay(500);  // wait for Serial to open
  Serial.println(compileTime);
  pinMode(admarkpin, OUTPUT);
  pinMode(wrmarkpin, OUTPUT);
  pinMode(ledpin, OUTPUT);
  pinMode(A0, INPUT_DISABLE); // disable digital keeper resistors
  adc->adc0->setAveraging(1 ); // set number of averages
  adc->adc0->setResolution(12); // set bits of resolution
  adc->adc0->setConversionSpeed(ADC_CONVERSION_SPEED::VERY_HIGH_SPEED); // change the conversion speed
  adc->adc0->setSamplingSpeed(ADC_SAMPLING_SPEED::HIGH_SPEED); // change the sampling speed
  
  adc->adc1->setAveraging(1 ); // set number of averages
  adc->adc1->setResolution(12); // set bits of resolution
  adc->adc1->setConversionSpeed(ADC_CONVERSION_SPEED::VERY_HIGH_SPEED); // change the conversion speed
  adc->adc1->setSamplingSpeed(ADC_SAMPLING_SPEED::HIGH_SPEED); // change the sampling speed
  if (!StartSDCard()) {
    // do fast blink forever

    do { // hang with blinking LED
      LEDON
      delay(100);
      LEDOFF
      delay(100);
    } while (1);

  }// end of  if (!StartSDCard())
  setSyncProvider(getTeensy3Time); // helps put time into file directory data
  #ifdef USEMTP  
  StartMTP();

  ARM_DEMCR |= ARM_DEMCR_TRCENA;
  ARM_DWT_CTRL |= ARM_DWT_CTRL_CYCCNTENA;
  #endif
}



void loop() {
  // put your main code here, to run repeatedly:
  char ch;
  if (Serial.available()) {
    ch = Serial.read();
    if (ch == 'l')  LogADC();
    if (ch == 's')  ShowADC();
    if (ch == 'd')  sdf.ls(LS_SIZE | LS_DATE | LS_R);
  }
  #ifdef USEMTP
  mtpd.loop();
  #endif
}



/*****************************************************
   This is the intervaltimer interrupt handler
   8/9/2020   Simplified to have main thread control
              logging.
              Changed per jonr to alternate ADCs
              This code takes about 220nS when logging
 ******************************************************/
volatile uint32_t dwtlast; 
volatile uint32_t maxinterval;
volatile uint16_t overflows;
void ADCChore(void) {
  uint16_t value;
  uint32_t dwt;
  dwt = ARM_DWT_CYCCNT; 
  ADMARKHI
  if (adcidx & 0x01) { //Read ADC0 and restart it   
    value = adc->adc0->readSingle();
    adc->adc0->startSingleRead(A0);    
 } else  { //Read ADC1 and restart it 
    value = adc->adc1->readSingle();
    adc->adc1->startSingleRead(A0); 
  }
  if (logging) { // Save result to buffer
    inbuffptr[adcidx] = value;
    adcidx++;
    if (adcidx >= ADBLOCKSIZE) {  // switch buffers at end
      if(sdbuffptr != NULL) overflows++;
      sdbuffptr = inbuffptr; // set up block for output
      if (inbuffnum == 0) { // swap input to other buffer
        inbuffptr = &adcbuff1[0];
        inbuffnum = 1;
      } else {
        inbuffptr = &adcbuff0[0];
        inbuffnum = 0;
      }
      adcidx = 0;
    }  // end of if(logging)
    ADMARKLO
    // ADMARHI to ADMARKLO takes about 180nS at 600MHz
    // So some oversampling may be possible
  } 
  //keep track of max clock cycles between interrupts
  if(dwt-dwtlast > maxinterval) maxinterval = (dwt-dwtlast);
  dwtlast = dwt;
}

/******************************************************
   Read 7 seconds of data from ADCs at 1 microsecond intervals
   Store the results in adcbuffer;
   note that MTP loop is not called during logging
 *****************************************************/

void LogADC(void) {
  uint32_t totalbytes = 0;

  Serial.println("Reading ADC Samples");
  inbuffnum = 0;
  inbuffptr = &adcbuff0[0];
  sdbuffptr = NULL;
  adcidx = 0;
  if (!OpenLogFile()) {
    Serial.print("Could not open log file.");
    return;
  }
  ADCTimer.priority(50); // medium-high priority
  ADCTimer.begin(ADCChore, 1.0); // start timer at 1 microsecond intervals
  delay(1);  // wait 1msec before starting logging
  dwtlast = ARM_DWT_CYCCNT;
  maxinterval = 0;
  logging = true;
  collectmillis = 0;  // reset the elapsedmillis timer
  do {
    if (sdbuffptr != NULL) { // when data in buffer, write to SD card
      WRMARKHI
  //    logFile.write(sdbuffptr, ADBLOCKSIZE*2);// save block of 16-bit words
      sdbuffptr = NULL;
      totalbytes += ADBLOCKSIZE;
    // Serial.print("."); // mark each 128KB block written
      WRMARKLO
    }
  }  while(collectmillis  < 7200); // A bit of extra time to make sure last buffer is written
  logging = false;
  ADCTimer.end();  // stop the timer
  logFile.truncate();  //truncate to amount actually written
  logFile.close();
  Serial.printf("\nADC Read %lu samples\n", totalbytes);
  Serial.printf("Maximum Sampling interval was %6.2f microseconds\n", (float)maxinterval/600.0);
}


bool OpenLogFile(void) {
  uint64_t alloclength;

  if (!logFile.open("Log1MS12B.dat",  O_RDWR | O_CREAT | O_TRUNC)) {
    return false;
  }
  alloclength = (uint64_t)200 * (uint64_t)(1024L * 1024l); //200MB

  if (!logFile.preAllocate(alloclength)) {
    Serial.println("Pre-Allocation failed.");
    return false;
  } else {
    Serial.println("Pre-Allocation succeeded.");
  }
  return true;

}


/******************************************************
   Display data from adcbuffer0 in lines of 20 values
   Only the first NUMTOSHOW values are displayed
 *****************************************************/
#define NUMTOSHOW   1000  // change to alter numbers output
void ShowADC(void) {
  uint32_t sendidx;

  Serial.println("ADC Data");
  // Sending the full 128K samples would  a long time!
  for (sendidx = 0; sendidx < NUMTOSHOW; sendidx++) {

    Serial.printf("% 5u", adcbuff0[sendidx]);
    if ((sendidx % 20) == 19) Serial.println();
  }
}


bool StartSDCard() {
  if (!sdf.cardBegin(SD_CONFIG)) {
    Serial.println("cardBegin failed");
  }
  if (!sdf.volumeBegin()) {
    Serial.println("volumeBegin failed");
  }
  if (!sdf.begin(SdioConfig(FIFO_SDIO))) {
    Serial.println("\nSD File initialization failed.\n");
    return false;
  } else  Serial.println("initialization done.");

  if (sdf.fatType() == FAT_TYPE_EXFAT) {
    Serial.println("Type is exFAT");
  } else {
    Serial.printf("Type is FAT%d\n", int16_t(sdf.fatType()));
  }
  // set date time callback function
  SdFile::dateTimeCallback(dateTime);
  return true;
}
/*****************************************************************************
   Read the Teensy RTC and return a time_t (Unix Seconds) value

 ******************************************************************************/
time_t getTeensy3Time() {
  return Teensy3Clock.get();
}

//------------------------------------------------------------------------------
/*
   User provided date time callback function.
   See SdFile::dateTimeCallback() for usage.
*/
void dateTime(uint16_t* date, uint16_t* time) {
  // use the year(), month() day() etc. functions from timelib

  // return date using FAT_DATE macro to format fields
  *date = FAT_DATE(year(), month(), day());

  // return time using FAT_TIME macro to format fields
  *time = FAT_TIME(hour(), minute(), second());
}

#ifdef USEMTP
void StartMTP(void){
  Serial.println("Starting MTP Responder");
  usb_mtp_configure();
  if(!Storage_init(&sdf)) {
    Serial.println("Could not initialize MTP Storage!");    
   } 
}
#endif

Hi mborgerson was wondering which MTP.h library you are using as I can't seem to find it through the Arduino library manager?

Best wishes Polly
 
Note there are a couple of versions of MTP... None of them are installed using the Arduino Library Manager.

The one that hopefully will be integrated soon (next release of Teensyduino) is located at: https://github.com/kurte/mtp_Teensy/

It started off based off of: https://github.com/WMXZ-EU/MTP_t4

Hi KurtE, Thanks for these :) If I were to use mtp_Teensy would the code be the same apart form the library name being 'MTP_Teensy.h' e.g.

Code:
 /*******************************************************/
// when USEMTP is defined, you can upload file with MTP,
// but you have to have the MTP library and modified USB files
#define USEMTP 

#ifdef USEMTP
#include "MTP_Teensy.h"
#include <Storage.h>
#include <usb1_mtp.h>

MTPStorage_SD storage;
MTPD      mtpd(&storage);
#endif
/*******************************************************/

Thanks in advance. Polly
 
They are different now. Not that hard to convert, but for example adding file systems to the list is slightly different in that you add them through the MTP object and not storage object.

The MTP object is automatically declared when you define the USB Type that includes MTP.

The current version is setup to use Teensyduino beta... SImplified version which handles SD cards, looks as simple as:
Code:
#include <SD.h>
#include <MTP_Teensy.h>

#define CS_SD BUILTIN_SDCARD  // Works on T_3.6 and T_4.1
//#define CS_SD 10  // Works on SPI with this CS pin
void setup()
{
  // mandatory to begin the MTP session.
  MTP.begin();

  // Add SD Card
  SD.begin(CS_SD);
  MTP.addFilesystem(SD, "SD Card");
}

void loop() {
  MTP.loop();  //This is mandatory to be placed in the loop code.
}
 
I think KurtE has answered your questions about MTP. It's really nice that the latest TeensyDuino install has simplified the usage of MTP. You no longer need to edit the core files to make the Serial Plus MTP USB device available.

As for your comment about jitter in the ADC sampling, see post #26 in the 1 MSPS thread at: https://forum.pjrc.com/threads/60299-1-MSPS-on-a-T4-Is-this-possible.
In that post, I use the ADC timer to trigger the collection. That means that there is no jitter in the collection, although there can still be some jitter in the time at which the data is stored. However, as long as the collection interval is longer than the potential storage delay, you won't have either jitter or missed samples. Having no jitter will definitely improve the data quality if you are planning to use it for spectral analysis.
 
Back
Top