Betrouwbare seriële communicatie A9<->M4<->Mega<----->Nano

Arduino specifieke Software
Berichten: 40
Geregistreerd: 19 Mei 2016, 15:37

Betrouwbare seriële communicatie A9<->M4<->Mega<----->Nano

Berichtdoor cuhka » 17 Dec 2016, 19:42

In mijn project maak ik gebruik van een UDOO Neo, een IoT platform met een ARM32 A9, en een M4 'Arduino UNO-compatible platform' [*]. De communicatie tussen de A9 en M4 gaat via een seriële poort. Op Linux is dat /dev/ttyMCC, en op de M4 is dat Serial. Verder gebruik ik een Arduino Mega in hetzelfde systeem. De M4 en de Mega zijn gekoppeld via Serial0 (op de M4) en Serial1 (op de Mega), door middel van een bidirectionele level shifter, aangezien de Neo 3.3v is en de Mega 5v.

De Neo M4 heeft er nogal wat moeite mee als de seriële output of input niet snel genoeg verwerkt kan worden. Bij niet snel genoeg lezen/schrijven kan deze gaan hangen. In ieder geval gaan er stukken data ontbreken, en ik weet niet hoe dit stabieler te krijgen.

Ik heb bijvoorbeeld een LCD scherm aan de Mega gekoppeld, en stuur die eenvoudige commando's als "LCD:PRINTAT0,0:Regel tekst". De M4 ontvangt dit, stuurt dit door naar de Mega, die zoekt de juiste module en die interpreteert weer de opdracht. Echter, als ik gelijk achter elkaar twee print commando's stuur (om twee regels tekst weer te geven) dan komen de commando's al niet goed aan. Ik heb dit 'opgelost' door een vertraging in het sturen van commando's in te bouwen. Tussen elk commando moet 100ms zitten. Het werkt, maar echt ideaal is het niet.

Deze combinatie communiceert vervolgens met een Arduino Nano via een RF24 transceiver. De tranceiver zit aangesloten op de Mega (vanwege [*]), en de de Mega stuurt inkomende berichten via M4 naar de A9, waar mijn proces het dus weer uit leest. Echter, als de Nano te snel data stuurt dan houdt het geheel het niet bij, en dus krijg ik hetzelfde probleem in omgekeerde richting, verkeerde data, en soms een hangend systeem.

Hoe krijg ik een beter systeem? Ik heb niet het idee dat er een automatische flow control is, ook niet op de A9, waar ik de seriële poort geprobeerd heb met port.setFlowControlMode(SerialPort.FLOWCONTROL_RTSCTS_IN | SerialPort.FLOWCONTROL_RTSCTS_OUT). En aangezien de M4<->Mega communicatie met twee draden plaatsvindt zal RTS/CTS helemaal niet werken dacht ik.

Op de M4 lees ik de seriële poort(en) zo uit:
Code: Alles selecteren
HardwareSerial& imxSerial = Serial;
HardwareSerial& megaSerial = Serial0;

....
QueueArray<String> commands;

void loop() {
  static String imxInput;
  static String megaInput;

  while (checkSerial(imxSerial, imxInput)) {
    commands.enqueue(imxInput);
    imxInput = "";
  }

  while (checkSerial(megaSerial, megaInput)) {
    imxSerial.println(megaInput);
    megaInput = "";
  }

  if (!commands.isEmpty()) {
    String command = commands.dequeue();
    megaSerial.println(command);
    dispatchCommand(command);

  }

  for (Module **mpp = modules; *mpp; mpp++) {
    (*mpp)->loop(now, imxSerial);
  }
}

bool checkSerial(Stream& stream, String& string) {
  int incoming = stream.read();
  while (incoming > 0 && incoming != '\r') {
    if (incoming != '\n') {
      string += (char)incoming;
    }
    incoming = stream.read();
  }

  return incoming == '\r';
}


Op de Mega is het natuurlijk niet veel anders:
Code: Alles selecteren
HardwareSerial& neoSerial = Serial1;

void loop() {
  const unsigned long now = millis();
  static String neoInput(80);

  if (checkSerial(neoSerial, neoInput)) {
    dispatchCommand(neoInput);
    neoInput = "";
  }

  for (Module **mpp = modules; *mpp; mpp++) {
    (*mpp)->loop(now, neoSerial);
  }
}

bool checkSerial(Stream& stream, String& string) {
  int incoming = stream.read();
  if (incoming > 0 && incoming != '\n' && incoming != '\r') {
    string += (char)incoming;
  }

  return incoming == '\r';
}


In de "(*mpp)->loop(now, neoSerial);" kan een module dus iets naar de seriële poort schrijven, wat dan weer door de M4 opgepakt moet worden en naar de A9 gestuurd moet worden.

Wat voor soort flow control is hierbij te creëren. Het is natuurlijk niet bekend wanneer een van de partijen wat wil versturen, en omdat elke module een aantal modules heeft die ook nog aandacht vergen, en output hebben zou ik niet zo weten hoe dit op te lossen. Tussen de M4 en de Mega zou ik natuurlijk nog twee extra draden kunnen spannen en die laag of hoog zetten of iets dergelijks, maar op de Neo (A9, M4) niet. Op de A9 wordt de output van de M4 wel via een interrupt gelezen.

Wat is een geschikte manier om er voor te zorgen dat de seriële communicatie tussen de verschillende onderdelen niet in de war raakt?

[*] Mwah, ze zijn niet altijd zo Arduino compatible als je zou willen

Advertisement

Gebruikers-avatar
Berichten: 2655
Geregistreerd: 06 Aug 2016, 01:03

Re: Betrouwbare seriële communicatie A9<->M4<->Mega<----->Na

Berichtdoor Koepel » 17 Dec 2016, 20:09

Lastig hoor. Er zijn teveel dingen aan de hand.
Normaal gesproken maak je een pakket, stopt er een checksum in, en controleer dat aan de andere kant, die vervolgens nog een acknowledge kan geven. Maar ik denk dat je beter eerst het hardwarematig moet verbeteren.

Waarom heb je een bidirectionele level shifter ? Dat hoort niet voor een seriele verbinding, omdat het signaal vaak hoog wordt gemaakt met een 10k pullup en ze zijn bedoeld voor I2C met 100kHz of 400kHz over korte afstanden.
De 10k pullup is veel te zwak, je hebt echt een hard signaal nodig.

Hoe lopen je ground verbindingen ? Kan er ergens een lus in de ground zitten ? Kun je een tekening maken waarin ook alle ground verbindingen zijn getekend ? Wat voor kabels gebruik je ? Hoe lang zijn de draden ? Hoe hoog zijn de baudrates ?

Berichten: 40
Geregistreerd: 19 Mei 2016, 15:37

Re: Betrouwbare seriële communicatie A9<->M4<->Mega<----->Na

Berichtdoor cuhka » 17 Dec 2016, 21:11

Waarom heb je een bidirectionele level shifter ?

Hier kan ik snel antwoord op geven: omdat de UDOO Neo 3.3v is, en de Mega 5v. De Neo kan geen 5v op zijn pinnen aan, dus vandaar een level shifter.

* De baudrates zijn 115200, voor alle seriële poorten.
* Neo GND en 3.3V gaan naar level shifter kant A GND en VCC
* Mega GND en 5V gaan naar level shifter kant B GND en VCC
* Kabels zijn standaard DuPont kabeltjes van 10cm.

Gebruikers-avatar
Berichten: 5043
Geregistreerd: 13 Mei 2013, 20:57
Woonplaats: Heemskerk

Re: Betrouwbare seriële communicatie A9<->M4<->Mega<----->Na

Berichtdoor nicoverduin » 17 Dec 2016, 21:30

Je zou het Xon/Xoff protocol kunnen toepassen. De ontvanger stuurt een xoff als deze bezig is een opdracht te verwerken en weer een xon als hij gereed is te ontvangen
Docent HBO Technische Informatica, Embedded ontwikkelaar & elektronicus
http://www.verelec.nl

Gebruikers-avatar
Berichten: 2655
Geregistreerd: 06 Aug 2016, 01:03

Re: Betrouwbare seriële communicatie A9<->M4<->Mega<----->Na

Berichtdoor Koepel » 17 Dec 2016, 22:36

Een bidirectionele level shifter is voor I2C. Die heeft geen harde uitgang, maar een 10k pullup weerstand, waardoor het signaal zomaar een beetje omhoog flubbert als het niet laag is.

Je hebt echter zeer korte draden, dus dan zou het met die 10k pullup misschien wel kunnen werken ;)

Dan ga ik toch denken in de software richting.
Wat weet je van de Neo M4 ? Wanneer er een nieuwe processor "Arduino compatible" wordt gemaakt, dan is dat in het begin meestal nog nauwelijks compatible.

Er is geen handshake, maar de Mega leest de bytes in met een interrupt. Ik dacht dat de buffer 64 byte is.
Wanneer de Neo M4 iets verstuurt dan weet de Neo M4 helemaal niet of het bij de Mega aangekomen is. De Neo M4 kan daar niet op gaan hangen.
Dus ik vraag me af wat er mis is met het lezen van de seriële poort door Neo M4. Hebben meer mensen daar last van ?

Gebruikers-avatar
Berichten: 5043
Geregistreerd: 13 Mei 2013, 20:57
Woonplaats: Heemskerk

Re: Betrouwbare seriële communicatie A9<->M4<->Mega<----->Na

Berichtdoor nicoverduin » 18 Dec 2016, 00:49

Meest extreem maar mogelijk een degelijke oplossing is een softwarematige XonXoff protocol inbouwen. Op het moment dat de Serial merkt dat de buffer vol is dan gaan de tekens gewoon verloren. Dus kun je beter een Xoff terugsturen naar de zender waardoor die even wacht. Geen delays heb je dan meer nodig enzo. Maar dan moet je wel een aanpassing maken in de Serial libraries )niet al te complex overigens).

En mogelijk heb je hier wat aan : http://www.udoo.org/docs-neo/Arduino_M4 ... ences.html
Docent HBO Technische Informatica, Embedded ontwikkelaar & elektronicus
http://www.verelec.nl

Berichten: 40
Geregistreerd: 19 Mei 2016, 15:37

Re: Betrouwbare seriële communicatie A9<->M4<->Mega<----->Na

Berichtdoor cuhka » 18 Dec 2016, 11:05

Een oplossing met een delay is inderdaad niet mogelijk. Het geheel moet door blijven gaan, en asynchroon werken. Op ieder moment kan een ander systeem gegevens sturen, en de ontvanger kan toevallig bezig zijn met het afhandelen van andere zaken. Het systeem op de A9 is interrupt driven, dus gegevens die daarheen worden gestuurd worden direct opgepakt.

Over het XON/XOFF heb ik wel wat gelezen, maar geen implementaties of voorbeelden, slechts referenties. Aangezien de communicatie bi-directioneel is en asynchroon lijkt het mij niet zo heel eenvoudig om het goed te krijgen. Wanneer zou je een XOFF moeten sturen? Je hebt geen idee dat of buffer vol is, wel of er nog input beschikbaar is, maar niet of er nog output buffer is. Alleen XON sturen als toevallig het proces de seriële poort uitleest lijkt mij ook een mis-and-hit oplossing, de een moet toevallig willen schrijven en dan moet toevallig de ander daar ook zijn.

Wellicht is het niet zo moeilijk als ik mij voorstel, maar in mijn gedachten krijg ik het idee nog niet rond.

Gebruikers-avatar
Berichten: 5043
Geregistreerd: 13 Mei 2013, 20:57
Woonplaats: Heemskerk

Re: Betrouwbare seriële communicatie A9<->M4<->Mega<----->Na

Berichtdoor nicoverduin » 18 Dec 2016, 12:22

Er zijn al meerdere topics geweest door de jaren heen over dit onderwerp. CTS/RTS is al eens geïmplementeerd. Voor de Xon/Xoff is men nog wel wat zoekende. Iedereen lijkt de kern van het probleem uit de weg te gaan omdat je de hardware library moet aanpassen.
Docent HBO Technische Informatica, Embedded ontwikkelaar & elektronicus
http://www.verelec.nl

Berichten: 4067
Geregistreerd: 16 Okt 2013, 14:31
Woonplaats: s hertogenbosch

Re: Betrouwbare seriële communicatie A9<->M4<->Mega<----->Na

Berichtdoor shooter » 18 Dec 2016, 12:32

kijk naar serialevent, je moet dan wel zorgen dat de loop snel doorlopen wordt dus geen delays.
paul deelen
shooter@home.nl

Gebruikers-avatar
Berichten: 5043
Geregistreerd: 13 Mei 2013, 20:57
Woonplaats: Heemskerk

Re: Betrouwbare seriële communicatie A9<->M4<->Mega<----->Na

Berichtdoor nicoverduin » 18 Dec 2016, 13:18

shooter schreef:kijk naar serialevent, je moet dan wel zorgen dat de loop snel doorlopen wordt dus geen delays.

Ik denk dat ie eerder hier naar moet kijken in hardwareSerial_private.h
cpp code
// Actual interrupt handlers //////////////////////////////////////////////////////////////

void HardwareSerial::_rx_complete_irq(void)
{
if (bit_is_clear(*_ucsra, UPE0)) {
// No Parity error, read byte and store it in the buffer if there is
// room
unsigned char c = *_udr;
rx_buffer_index_t i = (unsigned int)(_rx_buffer_head + 1) % SERIAL_RX_BUFFER_SIZE;

// if we should be storing the received character into the location
// just before the tail (meaning that the head would advance to the
// current location of the tail), we're about to overflow the buffer
// and so we don't write the character or advance the head.
if (i != _rx_buffer_tail) {
_rx_buffer[_rx_buffer_head] = c;
_rx_buffer_head = i;
}
} else {
// Parity error, read byte but discard it
*_udr;
};
}

En dan in veranderen in iets van

cpp code
// Actual interrupt handlers //////////////////////////////////////////////////////////////

void HardwareSerial::_rx_complete_irq(void)
{
#define XOFF 0x13
#define XON 0x11

if (bit_is_clear(*_ucsra, UPE0)) {
// No Parity error, read byte and store it in the buffer if there is
// room
unsigned char c = *_udr;
rx_buffer_index_t i = (unsigned int)(_rx_buffer_head + 1) % SERIAL_RX_BUFFER_SIZE;

// if we should be storing the received character into the location
// just before the tail (meaning that the head would advance to the
// current location of the tail), we're about to overflow the buffer
// and so we don't write the character or advance the head.
if (i != _rx_buffer_tail) {
_rx_buffer[_rx_buffer_head] = c;
_rx_buffer_head = i;
//
// controleer of de buffer nu wel vol is
//
i = (unsigned int)(_rx_buffer_head + 1) % SERIAL_RX_BUFFER_SIZE;
if (i == _rx_buffer_tail) {
//
// buffer is nu wel vol dus stuur een XOFF
//
Serial.write(XOFF); // nog niet echt efficient anders blijf je die codes heen en weer pompen Dus eenmalig de XOFF of XON versturen
} else {
Serial.write(XON);
}
} else {
// Parity error, read byte but discard it
*_udr;
};
}


Ik weet ook nog niet of je hier zo mee weg komt omdat je een Serial interrupt service routine gaat vullen met een serial write. Zit nog wel wat testwerk in.
Je zou nog kunnen kijken of je met Serial.available() kan testen of de buffer bijv. een paar bytes voor het max zit en dan de Xoff kan versturen. Maar daar lees ik nog een echt succesvolle verhalen over.
Docent HBO Technische Informatica, Embedded ontwikkelaar & elektronicus
http://www.verelec.nl

Volgende

Terug naar Arduino software

Wie is er online?

Gebruikers in dit forum: Robertkable en 47 gasten