SlashDevin / NeoSWSerial

Efficient alternative to SoftwareSerial with attachInterrupt for RX chars, simultaneous RX & TX
169 stars 42 forks source link

Added support for 32u4 #18

Open SRGDamia1 opened 6 years ago

SRGDamia1 commented 6 years ago

Set up 32u4/Leonardo using timer4, closing #3

SRGDamia1 commented 6 years ago

I've tested at all 4 baud rates and it seems to be working.

SlashDevin commented 6 years ago

Which Arduinos have you tried?

SRGDamia1 commented 6 years ago

The only 32u4 I have is an Adafruit Feather. I also have several 1284p boards and could probably get an Uno off of a coworker. I haven't figured out a great test between the boards yet; it's on my list..

SlashDevin commented 6 years ago

I'd feel better if you could try the UNO. Most users of NeoSWSerial have 328P boards. The differences look pretty clean, so it should work... But you know how that goes.

BTW, there is a pending pull for inverted logic. Would you be interested in folding that in? If you read the comments, you'll see that I was afraid that it would affect the timing. It may have to be a compile-time option, not a run-time inversion (i.e., extra cycles).

Thanks again. This will close out issue #3, too.

SRGDamia1 commented 6 years ago

My testing looks good, except at 38400. Actually, at that rate it is being really flaking for me even between two 1284p boards. I think you are right that there is no way 57600 would work without taking up a timer to get faster ticks.

I don't have anything to send out an inverted signal with except software serial, so I'm not sure how much that will prove with testing.

SlashDevin commented 6 years ago

I don't have anything to send out an inverted signal

LOL, me either... I could only test with two Arduinos using NeoSWSerial+inverted. That's what I meant. It would be enough to make sure the non-inverted timing was not affected. Thanks anyway.

SRGDamia1 commented 6 years ago

I'm sorry, I'm kind-of dumb. Could you explain how I should do the test between the two boards? I was playing with communicating between them, but I have a hard time keeping them in sync to dump the tests like you've done.

SlashDevin commented 6 years ago

Could you explain how I should do the test between the two boards? I was playing with communicating between them, but I have a hard time keeping them in sync to dump the tests like you've done.

Yes, this is a complex process that I have not documented. The main thing is to make sure all bit patterns are successfully sent and received. There are two configurations that should be tested:

  1. With one Arduino, using a direct wire connection between a UART (Serial3) and the NeoSWSerial pins (loopback wires). This tests whether simultaneous UART interrupts interfere with the NeoSWSerial PCI timing. Although not a "real" situation, it is a good MCU loading test.

  2. With two Arduinos. This test guarantees that a truly separate device (the 2nd Arduino in this case) can run at "full speed" (no inter-character wait time). In the above configuration, with simple loopback wires, the sketch "synchronizes" the sending and receiving. In this configuration, with 2 Arduinos, the timings (even the bit edges) are asynchronous. The 2nd Arduino is running the simple echo sketch:

    void loop()
    {
    if (Serial.available())
    Serial.write( Serial.read() );
    }

    This avoids having to sync two sketches, because either there is only one Arduino with loopback wires, or the second Arduino is always running the echo sketch.

In both configurations, there are two rounds of testing:

  1. In the first round of testing, the sketch waits for the echo of each character.
    With 2 Arduinos, this tests whether the send process works independently from the receive process (the 2nd Arduino echo doesn't begin until the 1st Arduino is done sending). With 1 Arduino + loopback wires, this tests whether the receive process works during the transmit process (each sent bit is simulataneously received).
    And by incrementing the sent character, all byte values can be tested.

  2. In the second round of testing, the sketch sends the same characters as fast as it can (no inter-character wait time). NeoSWSerial writes each byte immediately; there is no TX buffer.
    With 2 Arduinos, the echo from the second Arduino begins coming in after the 1st Arduino finishes sending the first byte. Then the 1st Arduino starts sending the second byte.
    With 1 Arduino + loopback wires, the first character arrives at the same time it is being sent.
    This tests the simultaneous sending and receiving parts of NeoSWSerial, synchronously (1 Arduino + loopback wires) and asynchronously (2 Arduinos).

You have to be careful about printing during this testing, because blocking at a Serial.print prevents reading characters out of the NeoSWSerial RX buffer. That's why you see Serial.flush() in the test program. This makes sure that the (debug) Serial TX buffer is empty, and that debug TX interrupts do not interfere with the test.

That's also why the test program has a buffer for the received characters. Each "phase" of testing runs without printing until characters stop coming (100ms receive gap). Characters stop arriving because all values have been sent. Then the received buffer is scanned to make sure it contains the expected values -- variations are displayed at that time, long after the test has completed.

The test sketch is also careful to constantly check for received characters while sending the test values, so that the RX buffer does not overflow after 64 characters.

torntrousers commented 6 years ago

I've been trying this pull request with an Adafruit Feather 32u4 board, using NeoSWSerial to read a GPS module. Sadly it doesn't work with NeoSWSerial attachInterrupt. I haven't been able to track down what goes wrong but some characters get scrambled. The same sketch + GPS module works fine on an Uno.

SlashDevin commented 6 years ago

@torntrousers, baudrate?

torntrousers commented 6 years ago

9600

SlashDevin commented 6 years ago

Sadly it doesn't work... some characters get scrambled.

Well, good thing I didn't pull this in yet. XD

At 9600, this should be no problem, though. There is an outstanding issue for reading binary data (not NMEA text) with attachInterrupt. This shouldn't be relevant to reading NMEA characters.

Does the polling style of reading work? That is, does NeoSWSerial work at 9600 if you don't attachInterrupt and simply check available from loop?

torntrousers commented 6 years ago

Yes the polling style works fine.

I think it is also working ok if in the interrupt routine the character is just appended to a character buffer which is dealt with in the main loop.

So the case where it doesn't work is when the character is given to the gps library within the interrupt routine. The gps library is http://arduiniana.org/libraries/tinygpsplus/.

So this snippet is the ISR that doesn't work:

static void handleRxChar( uint8_t c ) {
   tinyGps.encode((char) c);
}

At a guess: the GPS data comes in at 9600 baud and every second there are all at once half a dozen or so GPS sentences - this type of thing $GPGGA,123519,4807.038,N,01131.000,E,1,08,0.9,545.4,M,46.9,M,,*47 - so perhaps about 400 to 500 bytes, and each character is given one by one to tinyGps.encode. So maybe tinyGps.encode is too slow and delay the ISR and some bits get lost somewhere?

That does work fine on an Arduino Uno though, is the 32u4 a bit slower somewhere?

Update: Doh, my 32u4 is running at 8MHz and the Uno 16MHz so i suppose much slower. I have some 8MHz Arudino Mini's somewhere so I guess i need to try one of those to see if 8Mhz is enough or else there's not really anything wrong with this pull request.

SlashDevin commented 6 years ago

So this snippet is the ISR that doesn't work:

static void handleRxChar( uint8_t c ) {
  tinyGps.encode((char) c);
}

AHA!

(Sorry. Not sorry. ;) )

You are correct:

tinyGps.encode is too slow

If only there were a faster GPS library, perhaps one that was even smaller, more accurate and more reliable? Hmm? Get thee to NeoGPS. The example program NMEAisr.ino is the sketch you seek.

When NeoGPS is configured to run in the INTERRUPT processing style, it will safely parse the NMEA string quickly and produce coherent fix structures.

Trying to use TinyGPS from the ISR is going to be difficult. The values you are trying to get from TinyGPS (e.g., latitude) will be changing during the interrupt. Your main code could get the latitude from one second and the longitude from the next section.

Even worse, getting the latitude (a 4-byte float) may be interrupted in the middle the transfer. You could get two bytes of one float value and two updated bytes from a new float value. :P Bad juju.

This is another reason that NeoGPS provides complete fix structures when they become available. All the GPS fields from one update are packaged together in an interrupt-safe way. NeoGPS will almost certainly work on an 8MHz MCU.

torntrousers commented 6 years ago

NeoGPS? Never heard of it. Looks good though, I'll give it a try...

SlashDevin commented 6 years ago

I appreciate the extra comments in the trickier portions. I'm sure others will, too. Just a few other comments for you to review. Thanks!

SRGDamia1 commented 6 years ago

Thank you so much for the review! I definitely had the head and tail wrong for the buffer; that's now fixed.

I meant to close this pull request though, because there's still question about whether the 32u4 is working quite right. #26 has just the peek() and comments without the 32u4 changes.

SRGDamia1 commented 5 years ago

Ok. So I finally scrounged around for boards and did all the testing on all the different processor/speed combinations I could find. To test, I ran the "test" program on all the boards, both with loop-back wires and with the mega running the "bounce" program in between. Here's what I'm seeing:

AtMega328@16MHz (16MHz Arduino Pro): Works at 9600, 19200, 31250, and 38400 AtMega328@8MHz (8MHz Arduino Pro): Works at 9600 and 19200, not faster

AtMega1284p@8MHz (EnviroDIY Mayfly): Works at 9600 and 19200, not faster (Tx ok-ish but Rx isn't)

AtMega1280@16MHz (Seeeeduino Mega): Works at 9600, 19200, 31250, and 38400

AtMega32U4@8MHz (Adafruit Feather 32U4): Works at 9600 and 19200, not faster

That's all of the processor/board combinations I had to test.

SRGDamia1 commented 5 years ago

Got an AtMega32U4@16MHz (Arduino micro) going - Works at 9600 and 19200. Oddly at 38400 and 31250 it was iffy running asynchronously but spot-on with loop-back wires. I'm a little suspicious of my board, though. My computer kept dropping the serial port or saying it malfunctioned, so it might work fine on any other board.