Forum: Mikrocontroller und Digitale Elektronik AtTiny85 - Software UART die Zweite


von Max M. (maxmicr)


Lesenswert?

Guten Abend,

ich bin bei meinem Versuch, einen Software UART (zu erstmal TX) auf 
einem Attiny85 zu implementieren, erneut an eine Grenze gestoßen. Und 
zwar möchte ich das Ganze softwareseitig so aufbauen, dass man es 
ähnlich bedient wie einen normalen UART. Dafür hatte ich erst die Idee, 
das so zu machen (Pseudocode):
1
volatile char printChar;
2
volatile enablePrinting = 0;
3
ISR{
4
 if(enablePrinting == 1)
5
  print "printChar"...
6
 enablePrinting = 0; //aufhören nachdem der Char geprintet wurde
7
}
8
void setPrintChar(char c){
9
 enablePrinting = 1;
10
 printChar = c;
11
}
12
int main(void){
13
 while(1)
14
  setPrintChar('C');
15
  setPrintChar('B');
16
}

Das führte aber dazu, dass nicht ein C nach einem B folgte, sondern 
mehrere Cs und dann mehrere Bs kamen (ich denke, das liegt daran, dass 
die Funktion bereits wieder aufgerufen wird, obwohl der alte Char noch 
gar nicht übertragen wurde). Daher hab ich mir überlegt, dass mit einer 
Queue zu machen. Das sieht aktuell so aus:
1
#define F_CPU 1000000UL
2
3
#include <avr/io.h>
4
#include <avr/interrupt.h>
5
#include <stdlib.h>
6
7
volatile uint8_t enablePrinting = 0;
8
volatile char* queue;
9
volatile uint8_t queueLength = 0;
10
11
volatile int8_t position = -1;
12
volatile uint8_t pauseBits = 13;
13
volatile uint8_t needPause = 0;
14
15
ISR(TIMER1_COMPA_vect){
16
  if(enablePrinting == 1){
17
    if(needPause == 1 && pauseBits > 0){
18
      pauseBits--;
19
      if(pauseBits == 0){
20
        pauseBits = 13;
21
        needPause = 0;
22
      }
23
    } else {
24
      if(position == -1){ //start of transmission
25
        PORTB &=~(1<<PINB3); //Start Bit
26
        position++;
27
      } else {
28
        if(position == 8){ //end of transmission
29
          queue[queueLength-1] = (char)0;
30
          queueLength--;
31
          if(queueLength == 0)
32
            enablePrinting = 0;
33
          position = -1;
34
          PORTB |= (1<<PINB3); //Stop Bit HIGH
35
          needPause = 1;
36
        } else{
37
          if((queue[queueLength-1] >> position) & 0x01){
38
            PORTB |= (1<<PINB3);
39
          } else {
40
            PORTB &=~(1<<PINB3);
41
          }
42
          position++;
43
        }
44
      }
45
    }
46
  }
47
}
48
49
void initTimer(){
50
  TCCR1 |= (1<<CTC1); //Enable CTC
51
  TCCR1 |= (0b0010<<CS10); //CK/2
52
  OCR1A = 1;
53
  TIMSK |= (1<<OCIE1A); //enable compare interrupt
54
  queue = (char*)malloc(sizeof(char));
55
  sei(); //Enable Interrupts
56
}
57
58
void printChar(char c){
59
  queue[queueLength] = c;
60
  queueLength++;
61
  enablePrinting = 1;
62
}
63
64
int main(void)
65
{
66
  DDRB |= (1<<PINB3); //PINB1 as output
67
  PORTB |= (1<<PINB3); //PINB3 HIGH
68
  initTimer();
69
  while(1){
70
    printChar('C');
71
    printChar('B');
72
  }
73
}

Leider funktioniert das immer noch nicht. Jetzt kommt CCBBCCBB. 
Außerdem verbraucht mein Program nun statt 300Byte über 1KByte (liegt 
wahrscheinlich am malloc) :(

Wie würdet ihr das Problem lösen?

: Bearbeitet durch User
von Sebastian S. (amateur)


Lesenswert?

Ich mag einfach kein delay(), aber in diesem Falle ist es eine einfache 
Möglichkeit, festzustellen, ob Du Deine eigene Queue durcheinander 
bringst.

Du schriebst:
>void printChar(char c){
>  queue[queueLength] = c;
>  queueLength++;
>  enablePrinting = 1;
>}
Das heißt Du schreibst, ohne Rücksicht auf den Zustand der Queue, 
einfach einen neuen Wert in den Puffer.

Jeder Anfänger aber weiß das:
>  while(1){
>    printChar('C');
>    printChar('B');
>  }
Nicht nur schneller wie eine serielle Schnittstelle übergibt, sondern 
VIEL schneller.

Unabhängig davon, ob der Rest des Programmes funktioniert, solltest Du 
beim Einfügen neuer Zeichen, den Zustand kontrollieren.
Was Du bei einer vollen Queue machen sollst hängt aber vom gesamten, 
späteren Programm ab. Also abweisen oder warten.

Test Möglicherweise:
  while(1){
    printChar('C');
    delay ( 1000 );
    printChar('B');
    delay ( 1000 );
  }

: Bearbeitet durch User
von Sebastian S. (amateur)


Lesenswert?

Ich vergaß:

>queue = (char*)malloc(sizeof(char));

Ist natürlich Unsinn.
Immer eine feste Portion Speicher reservieren, widerspricht dem Konzept 
der dynamischen Speicherverwaltung.
In diesem Falle ist es sinnvoller einen fixen Speicherbereich (char 
xxx[cc]) zu reservieren.

Soweit mir bekannt reservierst Du einen Puffer mit der Länge 1!

Noch was:
Die/eine sinnvolle Verzögerung (1000) hängt natürlich von der zu 
erwartenden Übertragungsrate ab.

von Max M. (maxmicr)


Lesenswert?

Sebastian S. schrieb:
> Test Möglicherweise:

Wenn ich ein delay zwischendurch einfüge, klappt es wunderbar. Hätte es 
aber wahrscheinlich auch, wenn ich einfach eine Variable mit einem 
Char-Wert definiere und diesen Char dann einmal schreibe und in der 
Funktion jedes mal wieder ändere anstatt das über eine queue zu machen 
(wie im ersten Post erklärt).

Ich hab mal versucht, eine "String"-Funktion zu implementieren, die 
funktioniert auch. Nur das der eingegebene String rückwärts ausgegeben 
wird. Macht auch Sinn, jetzt brauch ich nur wieder eine Variable um von 
Oben nach Unten zu zählen. Nur, wo setzt man diese Variable auf das 
letzte Element? Wenn ein neues Element gerade hinzugefügt wird, kann ich 
die nicht setzen, weil sonst die Elemente ganz vorne nie ausgegebene 
werden.
1
void printString(char* str){
2
  while(*str){
3
    printChar(*str);
4
    str++;
5
  }
6
}
7
8
int main(void)
9
{
10
  DDRB |= (1<<PINB3); //PINB1 as output
11
  PORTB |= (1<<PINB3); //PINB3 HIGH
12
  initTimer();
13
  while(1){
14
    printString("\nolleH");
15
    _delay_ms(7500);
16
  }
17
}

Kann man das noch optimieren? 1.1Kbyte ist mir etwas viel nur für den TX 
vom UART.

: Bearbeitet durch User
von Karl M. (Gast)


Lesenswert?

Hallo,

ich frage mich warum du noch immer mit diesem Ansatz herum machst und 
nicht weiter kommst.

Es gibt die Implementation von Peter Dannegger für einen Software Uart 
mit FIFO, diese kann man verwenden und auch auf andere AVR µC anpassen.

von Max M. (maxmicr)


Lesenswert?

Karl M. schrieb:
> Es gibt die Implementation von Peter Dannegger für einen Software Uart
> mit FIFO, diese kann man verwenden und auch auf andere AVR µC anpassen.

Meinst du diese hier: Beitrag "AVR-GCC: UART mit FIFO"
So wie ich das sehe, benutzt er da Hardware-Peripherie die in einem 
ATtiny85 gar nicht vorhanden sind (z.B. ein USART_UDRE_vect)? Oder 
übersehe ich da was?

von Sebastian S. (amateur)


Lesenswert?

Damit es nicht so langweilig wird, ändern sich die Namen, die Atmel 
verwendet schon mal.

Davon sind sogar die Registerbelegung und die Namen der Unterbrechungen 
betroffen.

Also nix mit Copy & Paste.

Es besteht aber Hoffnung: ...suchet so werdet Ihr finden...

von Karl M. (Gast)


Lesenswert?

Max M. schrieb:
> Meinst du diese hier: Beitrag "AVR-GCC: UART mit FIFO"
> So wie ich das sehe, benutzt er da Hardware-Peripherie die in einem
> ATtiny85 gar nicht vorhanden sind (z.B. ein USART_UDRE_vect)? Oder
> übersehe ich da was?
Nein natürlich nicht.
Wenn man mit dem Schlagwort in die Mikrocontroller.net Suche geht, 
erhält man als ersten Link:
Beitrag "Software UART mit FIFO"

Den hatte hatte ich Dir in einem anderen Thread schon mal verlinkt.

von Jörg E. (jackfritt)


Lesenswert?

Ich benutze die Routinen von Peter schon länger auf meinen Tiny85 und 
habe bei langsamen Baudraten ohne Quartz keine Probleme, und das schöne 
ist man kann fast jeden Pin als TX nutzen.

von Sebastian S. (amateur)


Lesenswert?

@Max M³

Ob Du einzelne Zeichen in den Puffer schreibst oder einen ganzen String 
bleibt sich gleich.

So lange Du blind in den Puffer (und das auch noch bei einer Länge von 1 
Byte) schreibst, wird das nichts;-)
Vor allem, wenn Du ihn schneller füllst, wie die serielle Schnittstelle 
ihn leert.
Das sollte sich auch explizit, durch dem Test mit der langen 
Verzögerung, ergeben...

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.