luni
Well-known member
Maybe this helps to set up the differential ADC in principle. https://forum.pjrc.com/threads/57890-TeensyLoadcell-Direct-Loadcell-Readout-with-T3-2
Maybe this helps to set up the differential ADC in principle. https://forum.pjrc.com/threads/57890-TeensyLoadcell-Direct-Loadcell-Readout-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.
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.
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.
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
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
Thank you! I was working on a webserver with analog inputs but deleted most of the webserver code to simplify the debugging.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.
/*
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.
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
/*******************************************************/
// 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
/*******************************************************/
#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.
}
ADC Read 629145600 samples with 0 overflows
RingBuf max usage 97098 bytes of 131072
File max write time 5 us
2025-02-12 09:55 1258291200 Log1MS.dat
/********************************************************************
1.0 MegaSample T4.1 ADC using ADC timer to trigger the
ADC collection.
This version saves histogram data for timing interval
and ADC values
MJB 10/19/20 Modified to log 12-bit samples and save as uint16_t
JWP 02/11/25 Use SdFat RingBuf and (file).isBusy() function
********************************************************************/
#include "SdFat.h"
#include <TimeLib.h>
#include <ADC.h>
#include <RingBuf.h>
// instantiate a new ADC object
ADC *adc = new ADC(); // adc object;
// SdFS file system accepts both FAT and ExFAT cards
SdFs sd;
FsFile logFile;
#define RINGBUF_SIZE (128*1024) // JWP - 128KB buffer for 2MBps data rate
RingBuf<FsFile,RINGBUF_SIZE> rb; // JWP - ring buffer
#define SD_CONFIG SdioConfig(FIFO_SDIO)
const char compileTime [] = "MegaSample logger Compiled on " __DATE__ " " __TIME__;
//const int admarkpin = 1;
const int wrmarkpin = 0;
const int ledpin = 13;
// 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); // Also marks IRQ handler timing
#define LEDOFF digitalWriteFast(ledpin, LOW);
// buffers for histogram data
#define TMHISTOMAX 1000 // max interval 10000 cycles at 600MHz = 6 microseconds
#define ADCMAX 4096 // for 12-bit samples
uint32_t tm_histobuffer[TMHISTOMAX]; // for timing intervals up to 40.96mSec
uint32_t adc_histobuffer[ADCMAX];
// Saving 12-bit data as uint16_t
#define SAMPRATE 1000000
bool verboseflag = false; // true to show some output during sampling
bool writeflag = true; // true to write, false for no writes to SDC
volatile uint32_t totalsamples = 0;
const uint adcpin = A9; // my 2.5V precision reference
void setup() {
// put your setup code here, to run once:
Serial.begin(9600);
delay(500);
Serial.println(compileTime);
// activate ARM cycle counter
ARM_DEMCR |= ARM_DEMCR_TRCENA; // Assure Cycle Counter active
ARM_DWT_CTRL |= ARM_DWT_CTRL_CYCCNTENA;
pinMode(wrmarkpin, OUTPUT);
pinMode(ledpin, OUTPUT);
pinMode(adcpin, INPUT_DISABLE);
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::VERY_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())
rb.begin(&logFile); // JWP - associate logFile with RingBuf
setSyncProvider(getTeensy3Time); // helps put time into file directory data
}
void loop() {
// put your main code here, to run repeatedly:
char ch;
if (Serial.available()) {
ch = Serial.read();
if (ch == 'l') LogADC();
if (ch == 'a') ShowADCHisto();
if (ch == 't') ShowTmHisto();
if (ch == 'v') verboseflag = !verboseflag;
if (ch == 'w') writeflag = !writeflag;
if (ch == 'd') sd.ls(LS_SIZE | LS_DATE | LS_R);
}
}
void ShowSetup(void){
Serial.printf("Collection rate is %lu samples/second.\n",SAMPRATE);
Serial.print("Serial output during collection is ");
if(verboseflag) Serial.println("ON"); else Serial.println("OFF");
Serial.print("SDC Writes are ");
if(writeflag) Serial.println("ON"); else Serial.println("OFF");
}
// define the number of samples to collect as 10 M Samples
#define MAXSAMPLES 10*1024l*1024l
/*****************************************************
This is the ADC timer interrupt handler
******************************************************/
volatile uint32_t lastcycles;
volatile uint16_t overflows;
// This ISR runs in about 120nSec on T4.1 at 600MHz.
// It buffers the data, but SDC writes are user-selected
// timing and adc histogram values are saved in RAM
void adc0_isr() {
uint32_t tmdiff, thiscycles;
uint16_t adc_val;
LEDON;
if (totalsamples < MAXSAMPLES) { // sample until enough collected
thiscycles = ARM_DWT_CYCCNT;
tmdiff = thiscycles - lastcycles;
lastcycles = thiscycles;
totalsamples++;
// Collect ADC value and update the ADC Histogram data
adc_val = adc->adc0->readSingle();
// Save ADC data in buffer
rb.memcpyIn( &adc_val, sizeof(adc_val) );
// make sure we don't write outside histogram buffers
if (adc_val >= ADCMAX) adc_val = ADCMAX;
if (tmdiff >= TMHISTOMAX) tmdiff = TMHISTOMAX - 1;
// Skip the first two samples, as they often have
// weird timing values. Update histogram data
if (totalsamples > 2) {
tm_histobuffer[tmdiff]++;
adc_histobuffer[adc_val]++;
}
#if defined(__IMXRT1062__) // Teensy 4.0
asm("DSB");
#endif
} // end of if(totalsamples < maxsamples
LEDOFF;
}
/******************************************************
Read MAXSAMPLES from ADC at 1 microsecond intervals
Store the results in adcbuffer;
*****************************************************/
void LogADC(void) {
uint16_t lcount;
Serial.println("Reading ADC Samples");
totalsamples = 0;
overflows = 0;
memset(adc_histobuffer, 0, sizeof(adc_histobuffer)); // clear adc histogram counts
memset(tm_histobuffer, 0, sizeof(tm_histobuffer)); // clear timing histogram counts
ShowSetup();
if (!OpenLogFile()) {
Serial.print("Did not open log file.");
}
adc->adc0->stopTimer();
adc->adc0->startSingleRead(adcpin); // call this to setup everything before the Timer starts, differential is also possible
delay(1);
adc->adc0->readSingle();
// now start the ADC collection timer
adc->adc0->startTimer(SAMPRATE); //frequency in Hz
lastcycles = ARM_DWT_CYCCNT;
adc->adc0->enableInterrupts(adc0_isr);
lcount = 0;
uint32_t nWrites = 0;
uint32_t rbMaxUsed = 0;
uint32_t fileMaxWriteCycles = 0;
do {
// track max number of bytes in RingBuf
uint32_t n = rb.bytesUsed();
if (n > rbMaxUsed)
rbMaxUsed = n;
if (rb.bytesUsed() >= 512 && !logFile.isBusy()) { // when data in buffer, write to SD card
WRMARKHI
if (logFile) {
uint32_t start = ARM_DWT_CYCCNT;
rb.writeOut( 512 );
uint32_t cycles = ARM_DWT_CYCCNT - start;
// track longest write time, skipping first which always takes 2x longer than others
if (nWrites!=0 && cycles > fileMaxWriteCycles)
fileMaxWriteCycles = cycles;
nWrites++;
}
if(verboseflag && (nWrites%256)==0) {
Serial.print("."); // mark each 128KB written
if (lcount++ > 19) {
Serial.println();
lcount = 0;
}
}
WRMARKLO
}
} while (totalsamples < MAXSAMPLES);
adc->adc0->stopTimer();
if (logFile) {
rb.sync(); // JWP - write remaining data from RingBuf to file
logFile.truncate(); //truncate to amount actually written
logFile.close();
}
Serial.printf("\nADC Read %lu samples with %u overflows\n",totalsamples,overflows);
Serial.printf("RingBuf max usage %u bytes of %u\n", rbMaxUsed, RINGBUF_SIZE );
Serial.printf("File max write time %u us\n", uint32_t(fileMaxWriteCycles*(1E6/F_CPU_ACTUAL)));
}
bool OpenLogFile(void) {
uint64_t alloclength;
if(!writeflag) return false; // don't open file if not writing
if (!logFile.open("Log1MS.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;
}
void ShowTmHisto(void) {
uint32_t i;
Serial.println("Timing Histogram Data in ARM Cycle Counts");
for (i = 0; i < TMHISTOMAX; i++) {
if (tm_histobuffer[i] > 0) {
Serial.printf("%5lu %5lu\n", i, tm_histobuffer[i]);
}
}
Serial.println();
}
void ShowADCHisto(void) {
uint32_t i;
Serial.println("ADC Histogram Data in Counts");
for (i = 0; i < ADCMAX; i++) {
if (adc_histobuffer[i] > 0) {
Serial.printf("%5lu %5lu\n", i, adc_histobuffer[i]);
}
}
Serial.println();
}
bool StartSDCard() {
if (!sd.cardBegin(SD_CONFIG)) {
Serial.println("cardBegin failed");
}
if (!sd.volumeBegin()) {
Serial.println("volumeBegin failed");
}
if (!sd.begin(SdioConfig(FIFO_SDIO))) {
Serial.println("\nSD File initialization failed.\n");
return false;
} else Serial.println("initialization done.");
if (sd.fatType() == FAT_TYPE_EXFAT) {
Serial.println("Type is exFAT");
} else {
Serial.printf("Type is FAT%d\n", int16_t(sd.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());
}