USB Serial Checking and Speed

Status
Not open for further replies.

evengravy

Member
Hi,

I'm working on a project that uses serial to transmit a series of 18 bytes to Teensy 3.1/3.2 and I'm trying to do so as quickly as possible. I'm no master coder so would be grateful of any guidance to speed it up. Essentially I am using an ascii character to check against to indicate the start of the stream (47 or '/') and checking this in an if statement.

I've been trying to check the latency and it seems the actual loop is between 50uS and 320uS so that is more than quick enough, however I'm noticing that there is a null period of circa 10ms between the loops occurring (I'm checking this by toggling high/low and output pin at the start and end of the loop following the check for '/')

I have a suspicion that this delay is due to this character check at the beginning, might there be a better approach to buffering that could help?

Code:
void loop() {
   if (Serial.available()) {
     char c = Serial.read();
     if (c == '/') {
       for (int i=8; i >= 0; i--) {
         //do stuff here
       }
}
}

pic_143_4.gifpic_143_3.gif
 
Being setup to monitor pins like that - a second pin going hi/low around that pin could show the time spent on that test?

put "void yield() {}" in your code before setup() and see it that changes anything.
 
however I'm noticing that there is a null period of circa 10ms between the loops occurring

Something is very wrong. Teensy 3.2 and even regular 8 bit Arduino Uno should run so much faster than you're seeing.

(I'm checking this by toggling high/low and output pin at the start and end of the loop following the check for '/')

If you post a complete program, I could try running it here with my oscilloscope. But with only this code fragment, the best I can do is guesswork...


I can tell you we had a lengthy thread on this forum some time ago which turned out to be problems with a terrible Hantek USB scope. Even though it claimed 20 MHz bandwidth, in the end it turned out to be sampling at only 0.5 MHz.

If you're generating a short pulse with two digitalWrite(), please make sure you have a short delay between them, like 5 us.
 
Thanks Paul,

I suppose it could be the scope (100MHz Hantek DSO5102P) but it isn't one of the cheaper ones I've never had issues with it before and so far I've found it to be very good and it's well reviewed too.

https://www.circuitspecialists.com/...dso5102b-100mhz-digital-storage-oscilloscope/

I can post full code sure, I didn't do that previously as without the sending application it doesn't do much.

Essentially the project has a custom eight channel DAC (DAC8568) and 8x schmitt triggered gate outputs, its a controller for synthesis.

Code:
 //----------------------------------------------------------------------------------------------------//
 //----------------------------------------Max/Pd to CTRL----------------------------------------------//

 //------------------------------beta code John Harding, 2017------------------------------------------//
 
 //----------------------------------- streamlined code: Edwin Park, 201------------------------------//

 // Proposed data format concept... 17 bytes wide minimum. The USB packet size is 64 bytes. Maybe packet structure usage could be more efficient?
 // Arduino cookbook states "Messages can contain one or more special characters that identify the start of the message�this is called the header."
 // For an example see here: example 4.8: https://www.safaribooksonline.com/library/view/arduino-cookbook/9781449399368/ch04.html
 
 // [header] '/' character (ascii 47) for message start identification
 // [lowByte0][highByte0] //DAC channel 0
 // [lowByte1][highByte1] //DAC channel 1
 // [lowByte2][highByte2] //DAC channel 2
 // [lowByte3][highByte3] //DAC channel 3
 // [lowByte4][highByte4] //DAC channel 4
 // [lowByte5][highByte5] //DAC channel 5
 // [lowByte6][highByte6] //DAC channel 6
 // [lowByte7][highByte7] //DAC channel 7
 // [gateByte] //Gates array: 0 off, 1 on, eight gates used (one binary byte wide).
 
 // Note: serial object in Max allows you to set @chunk [int] (data list length) //also look at DTR (data terminal ready) function. Check Pd equivalent.
 
 //-------------------------------------HARDWARE NOTES---------------------------------------------//
 
 /*  Pin headers, looking from DAC side of main PCB
 
 // DAC OUT HEADER (BTM):
 
 | 6 | 4 | 2 | 0 | 1 | 3 | 5 | 7 |
 
 // GATE HEADER (TOP):
 
   | D+ | D- | GND | GND | GND | GND | GND | GND | GND | GND |
   | D+ | D- | D04 | D02 | D03 | D05 | D06 | D08 | D12 | D09 |
 
 */
 //-----------------------------------------------------------------------------------------------//
 
 //------------------------------------Includes---------------------------------------------------//
 #include <DA8568C.h>
 //----------------------------------------------------------------------------------------------//
 
 //-------------------------------------Constructor----------------------------------------------//
 DA8568C dac; // this implies using the PINs as defined in the Library (modified to match CTRL)
 // otherwise use constructor arguments like
 // DA8568C(int dataoutpin=3, int spiclkpin=4, int slaveselectpin=5, int ldacpin=6, int clrpin=7);
 //-----------------------------------------------------------------------------------------------//
 
 //-------------------------------------Constants-------------------------------------------------//
 
 #define NUM_GATES 8
 #define NUM_DACS 8
 
 const int gatePin[] = {9, 12, 8, 6, 4, 2, 3, 5};
 const int togglePin = 6;
 
 //-----------------------------------------------------------------------------------------------//
 
//-------------------------------------Variables-------------------------------------------------//

uint16_t dacVals[NUM_DACS];
boolean gateVals[NUM_GATES];

//-----------------------------------------------------------------------------------------------//

//-------------------------------------Main Program----------------------------------------------//
 void setup() {
  pinMode(togglePin, OUTPUT);
   dac.init(); //initialise the DAC
   initGates(); //initialise gate pins
   Serial.begin(115200); // 115200 is the default
 }
 
 void loop() {
   if (Serial.available()) {
     char c = Serial.read();
     if (c == '/') {
      digitalWriteFast(togglePin, HIGH); //added for latency testing, comment out if not required
       for (int i=NUM_DACS-1; i >= 0; i--) {
         uint16_t dacVal = readDAC();
         // Serial.print("DAC");
         // Serial.print(i);
         // Serial.print(": ");
         // Serial.println(dacVal);
         writeDAC(i, dacVal);
       }
 
       //read the gate byte at the end of the chain of bytes
       uint8_t gates = readGates();
       // Serial.print("gates: ");
       // Serial.println(gates);
       trigGates(gates);
       digitalWriteFast(togglePin, LOW); //added for latency testing, comment out if not required 
     }
   }  
 }
 
 //----------------------------------------Functions--------------------------------------//
 
 void initGates() {
   for (int i = 0; i < NUM_GATES; i++) {
     pinMode(gatePin[i], OUTPUT);
     digitalWrite(gatePin[i], LOW);
   }
 }
 
 uint8_t readGates() {
   while (true) {
     if (Serial.available()) {
       return Serial.read();
     }
   }
 }
 
 void trigGates(uint8_t gates) {
   for (int i = 0; i < NUM_GATES; i++) {

    boolean gateVal = gates & (0x01 << i);
    if (gateVal != gateVals[i]) {
      digitalWriteFast(gatePin[i], gateVal);
      gateVals[i] = gateVal;
    }
   }
 }
 
 uint16_t readDAC() {
   uint8_t bytes[2];
 
   int byte_count = 0;
   while (byte_count < 2) {
     if (Serial.available()) {
       bytes[byte_count++] = Serial.read();
     }
   }
 
   return (bytes[1] << 8) + bytes[0];
 }
 
 void writeDAC(int i, uint16_t dacVal) {
  if (dacVal != dacVals[i]) {
    dac.write(WRITE_UPDATE_N, i, dacVal);
    dacVals[i] = dacVal;
  }
 }

The DAC library is from here: https://github.com/oberling/DA8568C

If you need a sending application to test (Pure Data) I can send a patch over too. I'm sure I'm missing something simple.

Thanks a lot for having a look too, appreciated
 
Last edited:
Oh, now I can see much more....

Your program is waiting on data from your PC before it actually does anything. So the timing of loop() will depend on the latency of the application running on your computer. That latency will depend which operating system you're using. With some versions of Windows, programs tend to have 10 and 16 ms latency.
 
If you edit your loop() code like this, you'll be able to see how often it is really running, even when your PC doesn't send data.

Code:
 void loop() {
   digitalWriteFast(togglePin, HIGH);  // pulse the pin each time loop() runs, even if no data from the PC
   delayMicroseconds(5);
   digitalWriteFast(togglePin, LOW);
   delayMicroseconds(5);

   if (Serial.available()) {
     char c = Serial.read();
     if (c == '/') {
      digitalWriteFast(togglePin, HIGH); //added for latency testing, comment out if not required
       for (int i=NUM_DACS-1; i >= 0; i--) {
         uint16_t dacVal = readDAC();
         // Serial.print("DAC");
         // Serial.print(i);
         // Serial.print(": ");
         // Serial.println(dacVal);
         writeDAC(i, dacVal);
       }
 
       //read the gate byte at the end of the chain of bytes
       uint8_t gates = readGates();
       // Serial.print("gates: ");
       // Serial.println(gates);
       trigGates(gates);
       digitalWriteFast(togglePin, LOW); //added for latency testing, comment out if not required 
     }
   }
 }
 
Much better, thank you Paul.

On a larger scale, is the way that I'm doing the checking procedure efficient enough do you think? What happens when the '/' character is not the first character in the bundle for example? I'm guessing it's okay but more than willing to learn better.

Anyway, it seems the latency I was measuring is likely due to not sending nearly fast enough, certainly there's no bottleneck with the teensy end (amazing job, this thing is rapid). I'm using OS X and the data rate is minimum of 1ms in the application that sends, so certainly that is going to be the slowest aspect.

I accidentally found out my scope is 200mHz vs 100mHz it was sold as, seems the manufacturer popped on the 200mHz firmware from the factory (it goes down to 2ns)
 

Attachments

  • pic_145_1.gif
    pic_145_1.gif
    19.3 KB · Views: 88
This really depends on what you consider "efficient enough".

With USB, this sort of command-response design, where you wait for the other side to reply before beginning the next operation, is always slow. Then again, if you only need a couple hundred per second, maybe it's ok.

Many times a similar conversation has come up on this forum, about how to achieve better speed with USB. Maybe you can find some of those threads? In a nutshell, to get good USB performance you can't wait for a reply.
 
Status
Not open for further replies.
Back
Top