Arduino Nano UART Interrupt

Hallo Leute,

ich nutze einen Ardunino Nano (ATmega328p) und hänge leider fest.
Ich möchte, wenn etwas am Controller ankommt, das ein Interrupt auslöst. Im besten Fall soll das angekommene Byte gespeichert werden um weiter verwendet werden zu können.
Mit Serial.read hatte ich es zuerst umgesetzt jedoch kam es vor, das ich mehrmach ein Byte senden musste damit der Controller die Änderung erfasst.
Das ankommende Byte soll dazu dienen, zwischen den analog Pins zu switchen.

Ich habe im Datasheet und im Internet schon einiges gefunden jedoch ohne mein Problem zu lösen. Ich habe mein Programm mal hinzugefügt und ein Testprogramm, welches ich im Internet gefunden habe. In beiden Fällen löst der Interrupt nicht aus.

Vielen Dank vorweg.

Das ist mein Programm

#include <avr/sleep.h>
#include <avr/interrupt.h>
#include <avr/io.h>

int analogVal;
int statusU1;
int statusU2;
volatile char serialEing = 0;
unsigned long UVal1[16];
unsigned long UVal2[16];
int summe1;
int summe2;
float converted;
long internalVCC;
float ZeroVoltageVCC;
float sensedVoltage;
float difference;
int sensitivity;
volatile int flag1 = 0;

void setup() {

  ADMUX = bit (REFS0);
  ADCSRA |= bit (ADEN) | bit (ADIF) | bit (ADIE) | bit (ADPS0) | bit (ADPS1) | bit (ADPS2);

  UCSR0B |= (1 << RXEN0)  | (1 << TXEN0) | (1 << RXCIE0); // UART RX, TX und RX Interrupt einschalten
  UCSR0C |= (1 << UCSZ01) | (1 << UCSZ00)             ; // Asynchron 8N1

  UBRR0H = 0;
  UBRR0L = 103; //Baud Rate 9600
  sei();

}

ISR(ADC_vect) {
}

ISR(USART0_RXC_vect) //Habe es auch mit ISR(USART0_RX_vect) probiert
{
  flag1 = 1;
  serialEing = UDR0; 
}

void loop() {
  
  Serial.println(flag1);

  if ( serialEing == '1' ) {
    statusU1 = 1;
    statusU2 = 0;
    wandlung();
  }

  if ( serialEing == '2' ) {
    statusU1 = 0;
    statusU2 = 1;
    wandlung();
  }

  if (statusU1 == 1) {
    wandlung();
  }

  if (statusU2 == 1) {
    wandlung();
  }
}


void wandlung() {
  unsigned int i;
  summe1 = 0;
  summe2 = 0;

  for (i = 0; i < 16; i++) {

    if (statusU1 == 1) {

      ADMUX  = ADMUX & 0xF0;
      ADMUX |= 0x00;
      delay(1);
      sleep();

      UVal1[i] = ADCL | (ADCH << 8);
      summe1 += UVal1[i];

    }

    if (statusU2 == 1) {

      ADMUX  = ADMUX & 0xF0;
      ADMUX |= bit (MUX0);//0x01;
      delay(1);
      sleep();

      UVal2[i] = ADCL | (ADCH << 8);
      summe2 += UVal2[i];

    }

    if (i == 15) {
      if (statusU1 == 1) {
        conversion(summe1);
        Serial.println(converted);
        delay(7);
      }
      if (statusU2 == 1) {
        conversion(summe2);
        Serial.println(converted);
        delay(7);
      }
    }
  }
}

void sleep() {
  noInterrupts ();
  set_sleep_mode (SLEEP_MODE_ADC);
  sleep_enable();

  // start the conversion
  ADCSRA |= bit (ADSC);
  interrupts ();
  sleep_cpu ();
  sleep_disable ();
}


double conversion(int Val) {
  if ( x == 0) sensitivity = 219.8039216;
  else sensitivity = 40;
  internalVCC     = readInternalVcc();  //Interne Spannung = ext. Spannung
  ZeroVoltageVCC  = internalVCC * 0.001 / 2 ; //Sensor 0 V definiert bei halber Versorgungsspannung
  sensedVoltage   = (Val * internalVCC * 0.001) / 16368; //Interne Spg. auf Volt umrechnen und Digitalwert mit dem Verhältnis multiplizieren
  difference      = sensedVoltage - ZeroVoltageVCC; // Differenz zwischen Eingangswert und Nullpunkt
  converted   = difference * sensitivity; // Ausgabe der Eingangsspannung in V
  return converted;
}


long readInternalVcc() {
  long result;
  ADMUX   = _BV(REFS0) | _BV(MUX3) | _BV(MUX2) | _BV(MUX1); //Die 1,1V werden als Referenz zu AVcc genutzt
  delay(2);                                                    // Warte auf Vref
  ADCSRA |= _BV(ADSC);                                         // Konvertiere
  while (bit_is_set(ADCSRA, ADSC));
  result = ADCL;
  result |= ADCH << 8;
  //  result = 1126400L / result;                                  // AVcc in MV Vin = 1,1V
  result = 1127424L / result; //Vin = 1,101V
  return result;
}

Im Internet bin ich auf dieses Beispiel gestoßen jedoch tut sich bei mir nichts :frowning:

#include <avr/interrupt.h>
#include <avr/io.h>

volatile byte dummy = 0;

void uart_init(void)
{
  UCSR0B |= (1<<RXEN0)  | (1<<TXEN0) | (1<<RXCIE0);    // UART RX, TX und RX Interrupt einschalten
  UCSR0C |= (1<<UCSZ01) | (1<<UCSZ00)             ;    // Asynchron 8N1

  UBRR0H = 0;
  UBRR0L = 103; //Baud Rate 9600
}


ISR(USART_RXC_VECT)
{
  unsigned char b;
  b=UDR0;
  dummy = 1;
}

void setup()
{
  Serial.begin(9600);
  uart_init();
  sei();
}

void loop()
{
  if (dummy == 1) {Serial.print("UART");}
  delay(20);
}

Serieller Kommunikation sollte ein Arduino auch ohne Interrupt schnell genug hinterherkommen, einfach in loop() pollen. Ob Du dort dummy oder den seriellen Puffer oder die UART pollst, macht doch keinen zeitlichen Unterschied?

Sonst schau Dir mal den Sourcecode von Serial oder der Bibliothek an, die vom Nano normalerweise benutzt wird, einschließlich Bootloader.

Was passiert, wenn es mehrere

ISR(USART0_RX_vect)
{
  ...
}

gibt ( einen von dir und einen von "Arduino" ) ?

Im besten Fall soll das angekommene Byte gespeichert werden um weiter verwendet werden zu können.

Genau das macht die Arduino Software HardwareSerial doch schon für dich. Die speichert dir sogar bis zu 64 Bytes, bis du sie liest.

Wenn das Problem speziell mit dem Schlafen zusammenhängt: der Controller braucht einige Zeit bis er aufwacht. Wenn man also vom seriellen Interrupt Aufwachen und dann sofort Empfangen will kann es gut sein, dass die Zeichen am Anfang verloren gehen.

DrDiettrich hat geschrieben:

Serieller Kommunikation sollte ein Arduino auch ohne Interrupt schnell genug hinterherkommen, einfach in loop() pollen. Ob Du dort dummy oder den seriellen Puffer oder die UART pollst, macht doch keinen zeitlichen Unterschied?

Leider erkennt er nicht immer direkt ob was neues anliegt. Ich habe immer vermutet, dass das Programm zum Zeitpunkt wenn ich einen Kanal wechseln möchte einfach an einer anderen Stelle hängt.
Deshalb ging ich davon aus es per Interrupt lösen zu können, bisher jedoch ohne Erfolg.

Serenifly hat geschrieben:

Wenn man also vom seriellen Interrupt Aufwachen und dann sofort Empfangen will kann es gut sein, dass die Zeichen am Anfang verloren gehen.

Ich habe den SleepMode entfernt und es funktioniert einwandfrei.
Wenn ich einen delay() hinter das aufwachen aus dem SleepMode schreibe funktioniert es schon besser, jedoch auch nicht immer perfekt.
Für bessere Ergebnisse hat auch gesorgt, anstatt einer 1 oder 2 einfach 1111111111 oder 2222222222 zu schicken.

Ich habe den SleepMode entfernt und es funktioniert einwandfrei.

Da hatte Serenifly wohl den richtigen Riecher. / Erfahrung / Hintergrundwissen.

Wenn ich einen delay() hinter das aufwachen aus dem SleepMode schreibe funktioniert es schon besser, jedoch auch nicht immer perfekt.

Das ist eher erstaunlich. Wie ich es verstehe, wacht der Arduino vom Zeichenempfang auf, aber bis er wach ist, ist das Startbit schon durch und die ersten Zeichen werden verworfen wegen Framing Error o.ä...
Da du den Sender offensichtlich in der Hand hast, wäre es wohl besser, er sendet erst irgendwas ( z.B. eine binäre 0xFF = Stopbits ), wartet für die Dauer eines Zeichens und sendet dann das Befehlszeichen.

Ein 0xFF ohne Parity + mehr als die mindestens nötigen Stopbits kann der Empfänger entweder gar nicht oder höchstens als 0xFF erkennen und ist danach wach und munter und hat keine Probleme, sich auf das erste Zeichen danach zu synchronisieren.

michael_x:
Da hatte Serenifly wohl den richtigen Riecher. / Erfahrung / Hintergrundwissen.

Genau dazu gab es letztens ein Post. Ich wusste aber nicht mehr in welchem Thread genau

Jetzt habe ich es gefunden:
http://forum.arduino.cc/index.php?topic=258307.msg2385806#msg2385806

Man kann den Prozessor über den externen Interrupt an RX aufwecken. Da hat man aber auch das Problem, dass der Impuls zwar ausreicht um den Prozessor aufzuwecken, aber dann zu kurz ist um danach den Interrupt zu Triggern. Die Lösung wird gezeigt.

Serenifly:
Man kann den Prozessor über den externen Interrupt an RX aufwecken. Da hat man aber auch das Problem, dass der Impuls zwar ausreicht um den Prozessor aufzuwecken, aber dann zu kurz ist um danach den Interrupt zu Triggern.

Das ist IMO auch der falsche Weg. Wichtig ist bei der UART, daß der Taktgenerator so schnell hochfährt, daß das Startbit noch richtig erkannt wird. Das wären bei der maximalen Baudrate (Clk/2=8MHz=500kBaud) <32 Takte (2µs). Danach wird das Zeichen hardwaremäßig eingelesen, und erst am Ende (>320 Takte) kommt der Interrupt, der zum Speichern des empfangenen Zeichens verwendet wird.

Bei SPI oder I2C kann die Bitrate noch weit höher sein, da kann es eher zu Problemen kommen. Bei SPI ist man dann aufgeschmissen, wenn die ersten paar Bits unter den Tisch fallen. Bei I2C gibt es einen Frame-Fehler, die Übertragung wird nicht mit ACK abgeschlossen und muß wiederholt werden, es werden aber keine falschen Daten empfangen.