Author Topic: SerialUSB re-connection  (Read 1086 times)

don

  • Newbie
  • *
  • Posts: 2
SerialUSB re-connection
« on: January 18, 2018, 05:48:21 pm »
I'm trying to use SerialUSB (DigiCDC.h) on a Digispark with Linux. It works (and works better when I tell ModemManager to go away and leave the device alone!), but I find that if I connect to the device after it has output something, I don't get any output.

If I disconnect from the device and re-connect within a short time (or small amount of output), it's fine, but after a longer amount of output, I stop receiving data.

Here's a simple code example. This uses pin 1 as a light to show what it's doing (not connected to anything, just using the onboard LED) - the light comes on when we call SerialUSB.begin(), flicks off and back on when done, waits 5s, outputs "Hello, World!" and then prints 1, 2, 3, et c every second, toggling the light at each output line.

If I connect (with a terminal program, or just "cat /dev/ttyACM0") during the 5s wait, all is happy. If I wait until the light has started toggling, I get no output.

If I disconnect after output has started, it's OK if I reconnect quickly. But if I leave it a few seconds, again I get no further output.

If I insert "SerialUSB.end(); SerialUSB.begin();" before the SerialUSB.println() I get reliable output, but at the expense of delays while SerialUSB.begin() does its thing, and the ttyACM device is temporarily destroyed during that time (which of course also disconnects whatever is listening to it).

Any ideas? Ideally I'd like it to just sit there being a "serial" device that I can connect to any time to listen to its output.

-- don

Example code:

#include <DigiCDC.h>

void setup() {
  pinMode(1, OUTPUT);
  digitalWrite(1, 1);   // Light on during USB init
  SerialUSB.begin();
  digitalWrite(1, 0);   // Flick light when USB ready
  SerialUSB.delay(100);
  digitalWrite(1, 1);
  SerialUSB.delay(5000); // Pause to allow connect
  SerialUSB.println("Hello, world!");

}

long c = 0;
int heartbeat = 1;

void loop() {
  heartbeat = !heartbeat;  // Toggle light
  digitalWrite(1, heartbeat);
  SerialUSB.println(++c);  // And output count
  SerialUSB.delay(1000);
}

don

  • Newbie
  • *
  • Posts: 2
Re: SerialUSB re-connection
« Reply #1 on: January 19, 2018, 03:09:30 am »
So, terrible hack time. First, I made a little addition to DigiCDC:

Code: [Select]
*** DigiCDC.cpp.orig 2018-01-19 16:23:17.957346213 +1300
--- DigiCDC.cpp 2018-01-19 17:09:24.943709016 +1300
***************
*** 19,24 ****
--- 19,27 ----
 
  DigiCDCDevice::DigiCDCDevice(void){}
 
+ int DigiCDCDevice::waiting() {
+         return RingBuffer_GetCount(&txBuf);
+ }
 
  void DigiCDCDevice::delay(long milli) {
    unsigned long last = millis();
*** DigiCDC.h.orig 2018-01-19 16:37:29.212269926 +1300
--- DigiCDC.h 2018-01-19 17:06:05.573901031 +1300
***************
*** 42,47 ****
--- 42,48 ----
          void refresh();
          void task();
          void delay(long milli);
+         virtual int waiting(void);
          virtual int available(void);
          virtual int peek(void);
          virtual int read(void);

This adds a SerialUSB.waiting() call that returns how much unsent data is waiting in the transmit ring buffer. I use this as per the following little program, which listens for button presses on the four non-USB input lines:

Code: [Select]
// Little ditty to report button presses on four input lines
// Assumes inputs have pullup resistors, and buttons pull to ground.
//
#include <DigiCDC.h>

const int pins = 4;
const char pin[pins] = { 0, 1, 2, 5 }; // 3 & 4 are USB
char state[pins];

void setup() {
  for(int i = 0; i < pins; i++) {
    state[i] = 1; // Default to Up
    pinMode(pin[i], INPUT);
  }
  SerialUSB.begin();
}

void loop() {
  int i, r;

  // If there is waiting data, give it time to flush
  // If it hasn't, reset the USB.
  //
  if(i = SerialUSB.waiting()) {
    SerialUSB.delay(i * 5);
    if(SerialUSB.waiting() == i)
      SerialUSB.begin();
  }

  // Read all the button pins. If a key down, report it.
  //
  for(i = 0; i < pins; i++) {
    r = digitalRead(pin[i]);
    if(r != state[i]) { // If keydown ...
      if(r == 0) {
        SerialUSB.write('0' + i);
        SerialUSB.write('\n');
      }
      SerialUSB.delay(50); // cheap nasty debounce
      state[i] = r;
    }
  }
}

Basically, each time the code detects a +5V -> 0V transition on a button (i.e. grounding a pulled-up line), it prints the button number. Thus, you could do something like:

Code: [Select]
#!/bin/sh

device=/dev/ttyACM0
while true
do  if test -c $device
    then
        while read button
        do  test "$button" = "" && continue
            echo "Button $button pressed"
        done < $device
    fi
    sleep 1
done
Which waits for the device to come ready, the reads the button presses as they are sent.

But I can't say I like this solution; it works OK for this application because there is relatively little output; when we reset the device, there's a race between the receiving task re-opening the ttyACMn device and sending the output - if the device sends data before the host re-connects, the device will reset USB, which destroys the ttyACMn device, and the race starts again.

A proper indication of whether the port is ready would be better, as would not losing its marbles in the first place...