Forum: Mikrocontroller und Digitale Elektronik CHAR[] im Interrupt mit ext funktionen nutzen! HELP!


von Jörn (Gast)


Lesenswert?

Hy

Ich möchte bei einem INT0 Interrupt, das die Interrupt Routine etwas per 
UART versendet.
Die Daten sind char strings, dann in zwei Funktionen verschickt werden… 
Als Test verschicke ich es zur Zeit nach jeder Messung auch noch mal was 
später dann wegfallen sollte.
Leider kann ich nicht mehr in die Chars schreiben, wenn ich sie global 
def. auch mit volatile bzw. wenn ich sie in der Main def, findet die 
Interrupt Routine nix!!

Was kann ich tun!!

#define   F_CPU 8000000UL
#include  <avr/io.h>
#include  <stdio.h>
#include  <stdlib.h>
#include   <inttypes.h>
#include   <avr/interrupt.h>
#include   <avr/signal.h>

#define BAUD     38400UL
#define UBRR_BAUD  ((F_CPU)/(16*(BAUD))-1)

unsigned char volatile   LFC[6];
unsigned char volatile  TC[6];
unsigned char volatile  LFKC[6];

int uart_putc(unsigned char c)
{
  while (!(UCSRA & (1<<UDRE)));

  UDR = c;
  return 0;
}

int uart_puts( char* str )
{
  while( *str )
    uart_putc( *str++ );
  return 0;
}


int main (void)
{

double      LF = 0;
double      T = 0;
double       LFkomp = 0;

UCSRB   |= (1 << TXEN) | ( 1 << RXEN );  // UART TX, RX einschalten
 UCSRC   |= ( 1 << URSEL )|( 3<<UCSZ0 );  // Asynchron 8N1
 UBRRH  = (uint8_t) (UBRR_BAUD>>8);      // USART Baud
  UBRRL  = (uint8_t) UBRR_BAUD;

ADCSRA = _BV(ADEN) | _BV(ADPS1) | _BV(ADPS2);  // AD anschalten & 
Prescaler 64 Takt = (CLK/Prescaler) 50-200kHz

  MCUCR = _BV(ISC00);
  GICR  = _BV(INT0);
  sei();
  uart_puts("System wurde gestartet\r");

  for (;;)
  {

  ADMUX = 0;      // AD Kanal A.0
  ADCSRA |= _BV(ADSC);    // AD Messung starten
         while (ADCSRA & _BV(ADSC))    // bis ein Wert eingelesen ist 
warten
  {}
  LF = ADCW;

  …

  ADMUX = 0x01;                 // AD Kanal A.1
  ADCSRA |= _BV(ADSC);            // AD Messung starten
  while (ADCSRA & _BV(ADSC) )     // bis ein Wert eingelesen ist
  {}
  T = ADCW;

…..

  LFkomp =….;

  dtostrf(LF,6,3,LFC);
  dtostrf(T,6,3,TC);
  dtostrf(LFkomp,6,3,LFKC);
  uart_puts("LFC");
  uart_puts(TC);
  uart_puts(LFKC);
  }
}

SIGNAL (SIG_INTERRUPT0)
{
    uart_puts("LFC");
    uart_puts(TC);
    uart_puts(LFKC);
}

von johnny.m (Gast)


Lesenswert?

Könntest Du vielleicht mal präzisieren, wo genau die Probleme auftreten? 
BTW: SIGNAL ist veraltet. Bitte ISR verwenden und, falls noch nicht 
geschehen, auf aktuelle WINAVR-Version updaten. Außerdem haben serielle 
Ausgaberoutinen in einem Interrupt-Handler normalerweise nichts zu 
suchen.

von johnny.m (Gast)


Lesenswert?

Übrigens: Du hast Deine Strings alle mit 6 Elementen deklariert, hast 
aber beim Aufruf von dtostrf 6 Stellen als "width" angegeben. Das passt 
nicht! Für den Nullterminator brauchst Du eine zusätzliche Stelle. Alle 
Arrays müssen in dem Fall als "unsigned char NAME[7]" deklariert werden, 
sonst schreibt dtostrf den Nullterminator ins Speichernirvana und die 
Ausgaberoutine findet ihn nicht.

von Karl heinz B. (kbucheg)


Lesenswert?

Geh noch größer. Vorzeichen, Dezimalpunkt etc.
Ist es meistens unklug, die char Felder zu exakt
dimensionieren zu wollen (ausser man ist in grosser
Speichernot). Mach die Felder 15 oder 20 Zeichen gross.
Ob die Bytes jetzt brach liegen oder ob dort char Felder
angelegt sind, ist dem RAM aber sowas von Wurscht.

von Jörn (Gast)


Lesenswert?

vielen dank für die schnellen antworten.

C:/WinAVR/avr/include/avr/signal.h:36:2: warning: #warning "This header 
file is obsolete.  Use <avr/interrupt.h>."

woher bekomme ich die neueste? hab die erst vor 1 Woche runtergeladen...

von Karl heinz B. (kbucheg)


Lesenswert?

Sagte er doch:

signal.h: Warnung,... Diese File ist veraltet. Bitte benutze
  <avr/interrupt.h>

Du möchtest vielleicht mal hier nachlesen.
http://www.mikrocontroller.net/articles/AVR-GCC-Tutorial#Veraltete_Funktionen_zur_Deklaration_von_Interrupt-Routinen

von Jörn (Gast)


Lesenswert?

ok danke.

Mein Problem lag hoffentlich an der Größe der char Variablen und er hat 
daher nie etwas in die Interruptfunktion übergeben können...

werde es morgen mal testen. aber globale Variablen sollten doch kein 
Problem in der Interrupt Funktion sein oder?

Bzw wie würde es besser gehen? es soll halt beim INTO auslösen die Daten 
gesendet werden...

von johnny.m (Gast)


Lesenswert?

@Karl Heinz:
Afaik ist die im Aufruf von dtostrf angegebene "width" aber inkl. 
Dezimalpunkt und eventuellem Vorzeichen. Das sagt mir zumindest der 
Kommentar in der stdlib.h. Ob es bei seinen zu erwartenden Zahlenwerten 
ausreicht, mit 6 Stellen (davon drei Nachkomma und ein Dezimalpunkt und 
u.U. zusätzlich noch ein Vorzeichen, bleibt evtl. nur eine Stelle vor 
dem Dezimalpunkt übrig) darzustellen, kann man anhand des geposteten 
Codes nicht nachvollziehen, da die ganzen Zwischenrechnungen fehlen.

@Jörn:
Code von der Größe demnächst bitte als Anhang schicken! Und bei 
kleineren Codestücken auf eine übersichtliche Formatierung achten (Code 
kannst Du auch im Fenster in Farb-Syntax-Darstellung posten, wenn Du ihn 
in
1
 und
 einschließt).

von johnny.m (Gast)


Lesenswert?

Na, das hat ja geklappt. Also C-Code in "eckige Klammer auf, großes C, 
eckige Klammer zu" und am Ende "eckige Klammer auf, Schrägstrich (/), 
großes C, eckige Klammer zu"...

von Karl H. (kbuchegg)


Lesenswert?

> Afaik ist die im Aufruf von dtostrf angegebene "width" aber inkl.
> Dezimalpunkt und eventuellem Vorzeichen.

Ja schon. Die Zahl vor dem Punkt ist die Gesamtzahl
an Stellen, also die Feldbreite.
Die spannende Frage ist immer nur: Was passiert, wenn die
angegebene Feldbreite nicht ausreicht, weil meine auszugebende
Zahl da partout nicht hineinpasst.
Sich darauf zu verlassen, dass printf niemals mehr als die
Feldbreite schreibt, ist nicht weise. Es tut es nämlich nicht.
Wenn das Formatierfeld nicht gross genug ist, dann schreibt printf
mehr. Ein 4 stelliger int passt nun mal nicht in einen %3d, also
werden auch 4 Zeichen geschrieben.

von johnny.m (Gast)


Lesenswert?

@Karl Heinz:
Er benutzt aber gar kein printf, sondern nur dtostrf...

von Karl heinz B. (kbucheg)


Lesenswert?

> Er benutzt aber gar kein printf, sondern nur dtostrf...

<Hochscroll>
<Rotwerd>

Uh, oh, ähm.
Mein Fehler

Grob geschätzt: Es ist bei dtostrf auch nicht
anders.

von Jörn (Gast)


Lesenswert?

hy funktioniert alles aber suuuper langsam!!

Die  umwandlung von double in das char mit dtostrf()... dauert 10ms 
jeweils!!

das ist viel zu lange!!!

wie kann ich die double Werte per UART einfacher bzw. schneller 
übertragen??
max 2 Ziffern vor dem Komma und 2 dahinter reichen aus...

von johnny.m (Gast)


Lesenswert?

10 ms bei 8 MHz (entspricht 80000 CPU-Takten) kommt mir auch ein 
bisschen arg viel vor. Da müsste sogar sprintf schneller sein. Woher 
nimmst Du die Gewissheit, dass das so lange dauert? Bist Du sicher, dass 
der µC mit 8 MHz läuft?

von Jörn (Gast)


Lesenswert?

hab vor und hinter dem befehl PINB=0x02 an aus gemacht und das ganze mit 
dem Oszi gemessen...

fused sind in
sut1 nix
sut0 haken
cksel3 haken
cksel2 nix
cksel1 haken
cksel0 haken

von johnny.m (Gast)


Lesenswert?

Bin ich blind, oder warum finde ich in dem Code nirgends eine Anweisung 
"PINB = 0x02"? Was für ein Controller ist das überhaupt?

von Karl heinz B. (kbucheg)


Lesenswert?

Zeig nochmal das jetzige Pgm vollständig.
10ms ist in der Tat extrem langsam.

von Jörn (Gast)


Lesenswert?

[Code]
#define   F_CPU 8000000UL
#include  <avr/io.h>
#include  <stdio.h>
#include  <stdlib.h>
#include   <inttypes.h>
#include   <avr/interrupt.h>

#define BAUD     38400UL
#define UBRR_BAUD  ((F_CPU)/(16*(BAUD))-1)

char LFC[15];
char TC[15];
char LFKC[15];

int uart_putc(unsigned char c)
{
  while (!(UCSRA & (1<<UDRE)));

  UDR = c;
  return 0;
}

int uart_puts( char* str )
{
  while( *str )
    uart_putc( *str++ );
  return 0;
}


int main (void)
{

double         Faktor = 0.0048828125;
double        LF = 0;
double        T = 0;
double         LFkomp = 0;

  UCSRB   |= (1 << TXEN) | ( 1 << RXEN );  // UART TX, RX einschalten
   UCSRC   |= ( 1 << URSEL )|( 3<<UCSZ0 );  // Asynchron 8N1
   UBRRH  = (uint8_t) (UBRR_BAUD>>8);      // USART Baud
    UBRRL  = (uint8_t) UBRR_BAUD;

  ADCSRA = _BV(ADEN) | _BV(ADPS1) | _BV(ADPS2);  // AD anschalten & 
Prescaler 64 Takt = (CLK/Prescaler) 50-200kHz
  MCUCR = _BV(ISC00)|_BV(ISC01);
  GICR  = _BV(INT0);
  sei();
  uart_puts("System wurde gestartet\r");
  DDRB = 0x03;

  for (;;)
  {
                      // Rohleitfähigkeit einlesen
                    // 0-5V 0-24 mS =>
    ADMUX = 0;              // AD Kanal A.0
    ADCSRA |= _BV(ADSC);        // AD Messung starten
    while (ADCSRA & _BV(ADSC))      // bis ein Wert eingelesen ist 
warten
    {}
    LF = ADCW;              // Weise den Wert b zu
    LF = LF*(double)Faktor;
    LF = LF * 5.42857;          // Umrechnung Spannung pro mS

                      // Temperatur einlesen
                      // 0-5V ,0-60 C
    ADMUX = 0x01;                 // AD Kanal A.1
    ADCSRA |= _BV(ADSC);            // AD Messung starten
    while (ADCSRA & _BV(ADSC) )     // bis ein Wert eingelesen ist
    {}

    T = ADCW;              // Weise den Wert b zu
    T= (T*Faktor)*28.869-27.713;

    int loesung = 1;
                      // Acetat = 1
                      // Bicarbonat = 2
    if(loesung == 1)
      {
      double a = 0.021;
      LFkomp = LF+LF*a*(T-25);
      }

    else if(loesung == 2)
      {
      double a = 0.023;
      LFkomp = LF+a*(T-25);
      }
    PORTB=0x01;
    dtostrf(LF,6,4,LFC);
    PORTB=0x00;
    dtostrf(T,6,4,TC);
    dtostrf(LFkomp,6,4,LFKC);
    }
}

ISR (SIG_INTERRUPT0)
{

    uart_puts("\r");
  //  uart_puts( "Rohleitfähigkeit  : " );
    uart_puts(LFC);
  //  uart_puts("[mS/cm]\r");
      uart_puts("\r");
  //  uart_puts( "Temperatur        : " );
    uart_puts(TC);
  //  uart_puts("[°C]\r");
    uart_puts("\r");
    //  uart_puts( "Endleitfähigkeit  : " );
    uart_puts(LFKC);
  //  uart_puts("[mS/cm]\r");
    uart_puts("\r");

}




[\Code]

von Jörn (Gast)


Lesenswert?

Atmel MEga8 und intern auf 8Mhz

von Karl heinz B. (kbucheg)


Lesenswert?

Hmm.
In der Tat, das sieht soweit nicht so schlecht aus.
Und wenn du sagst, dass der Puls 10ms breit ist, glaub ich dir
das (obwohl ichs mir immer noch nicht vorstellen kann,
bist du sicher, dass du nicht die Zeit zwischen 2 Pulsen gemessen
hast?)

Da du anscheinend sowieso immer 2 Vorkommastellen und
2 Nachkommastellen hast, könnte man die dtostrf() auch
ersetzen:

void Format( double d, char* String )
{
  int Vorkomma = (int)d;
  int Nachkomma = (int)( ( d - Vorkomma ) * 100 );

  sprintf( String, "%d.%02d", Vorkomma, Nachkomma );
}

Wenn der sprintf zu viele Resourcen verbraucht, dann
musste man dafür einen Ersatz mit itoa und temporären
Strings die dann zum Gesamtergebnis zusammengesetzt werden
basteln.

von Karl heinz B. (kbucheg)


Lesenswert?

Das
> void Format( double d, char* String )
> {
>   int Vorkomma = (int)d;
>   int Nachkomma = (int)( ( d - Vorkomma ) * 100 );
>
>   sprintf( String, "%d.%02d", Vorkomma, Nachkomma );
> }

ist vielleicht gar nicht so gut. Könnte Ärger mit der
Rechengenauigkeit und Rundungsfehlern geben.

Eher mal in die Richtung arbeiten:
1
void Format( double d, char* String )
2
{
3
  int Zahl = (int)( d * 100 + 0.5 );
4
  sprintf( String, "%d.%02d", Zahl / 100, Zahl % 100 );
5
}

Achtung: Beide Funktionen berücksichtigen kein negatives
Vorzeichen!

von Jörn (Gast)


Lesenswert?

Ich lese immer das +5V Signal.

von Dirk (Gast)


Lesenswert?

Hallo, meiner Meinung nach solltest du dein Programm ein bischen 
abaendern.

1. Nur ein Flag in der ISR setzen und in der Main abfragen z.B.:

Pseudo AVR GCC Code
1
#define TRANSMIT 0
2
volatile u8 TRANSMIT=0;
3
4
ISR(xxx)
5
{ 
6
  status_flagreg |= (1<<TRANSMIT);
7
}
8
9
10
int main(void)
11
{
12
  for(;;){
13
  if(status_flagreg & (1<<TRANSIMT)){
14
   uart0_out("uzibuz");
15
  }
16
  }
17
}

2. In der Codesammlung liegt von Peter Danneger seine Output Routinen 
für den 8051er. Eine Portierung für AVR GCC hab ich in dem selben Thread 
hinterlegt. Die Funktion sind sehr schnell und Code sparend.

Gruß,
Dirk

von Jörn (Gast)


Lesenswert?

mhh es muss sofort ohne verzögerung gesendet werden. von daher wär das 
leider im schlimmsten Fall mit fast 30ms zu spät.

die output routine werde ich mal testen...

von Karl heinz B. (kbucheg)


Lesenswert?

> mhh es muss sofort ohne verzögerung gesendet werden. von daher wär
> das leider im schlimmsten Fall mit fast 30ms zu spät.

Bei solch engen Zeit-Vorgaben müsste man sinnvollerweise sowieso das
Pgm umbauen:
  In der Hauptschleife wird gar nicht mehr auf das Ende des ADC
  gewartet, sondern der wird nur noch angestossen. Wenn ein
  ADC fertig ist, meldet er sich mit einem Interrupt zurück,
  woraufhin der Wert ausgelesen wird und für die UART bereit-
  gelegt wird.

  Die ganzen double-Rechnereien fallen dem Rotstift zum Opfer
  und werden durch fixed-Point Arithmetik ersetzt.

Die Hauptschleife in der main überwacht nur noch, ob ein
ADC nichts zu tun hat und stösst wieder eine Wandlung an.
Daanch überprüft sie ob eine Interrupt-Anfrage nach den
ADC Werten vorliegt. Wenn ja initiiert sie die Übertragung,
die auch wieder Interruptgetrieben von alleine so schnell
wie möglich von statten geht.

Dadurch kriegst du ein Pseudo-Multitasking, in dem niemand
auf jemand anderen warten muss und trotzdem alles so schnell
wie möglich abgearbeitet wird (und sich die CPU immer noch 95%
ihrer Zeit langweilen wird).

von Jörn (Gast)


Lesenswert?

hy

ja das Problem mit dem unterbrechen des ADC hatte ich auch schon im 
Kopf. leider machten mir nur die Chars Probleme und so folg das cli 
wieder raus :-)

1. Wenn ich am anfang von For schleife sei() und am ende CLI mache merk 
er sich dann den zwischendurch ausgelösten IRQ und löste ihn aus oder 
nur wenn er im "rücksprung"  hinter cli und in der For() schleife nach 
oben ist zu sei springt? also

...
for (;;)
  {
   sei();
    ADMUX = 0;              // AD Kanal A.0
....
  cli();
  }

2. benötigt werden nur zahlen von 0-50 8. die Nachkommastellen sollten 
auf 2 gerundet werden so das nur XX.XX übergeben werden müssen. das 
Protokol am PC kann ich ja selber definieren.

Kann ich nicht die 2 Vorkommaziffern und 2 Nachkommazifffern jeweils in 
einem 8bit Wert senden und am PC einfach zusammensetzen durch ein 
Protokoll...

also AA senden, BB senden....

-> AA.BB und CC.DD

könnte ja die Vorkommazahl durch runden bekommen (->int) und die nach 
kommastellen durch
Zahl *100- int*100....

oder geht das einfacher?




Bitte melde dich an um einen Beitrag zu schreiben. Anmeldung ist kostenlos und dauert nur eine Minute.
Bestehender Account
Schon ein Account bei Google/GoogleMail? Keine Anmeldung erforderlich!
Mit Google-Account einloggen
Noch kein Account? Hier anmelden.