mikrocontroller.net

Forum: Compiler & IDEs UART senden ohne Timer emulieren


Autor: Paul H. (powl)
Datum:

Bewertung
0 lesenswert
nicht lesenswert
Hi!

Ist es irgendwie möglich in C eine kleine Routine zu schreiben die mir 
an einem Pin ein paar Bytes über Uart senden kann? Das ganze sollte ganz 
einfach ohne Timer funktionieren. D.h. es müssen Warteschleifen ran was 
natürlich am besten in Assembler ginge. Aber es ist doch sicher möglich 
sich kompilierte Programm anzugucken und daraus die Wartezeit bzw. die 
Anzahl der zu wartenden Takte zu bestimmen und dann mit den Funktionen 
_delay_loop_1 und 2 umzusetzen.

Ist das möglich, oder kann man nicht sicher stellen dass der gcc den 
Code immer gleich umsetzt, gleich wie andere Programmteile des AVRs 
aussehen oder in welchem Programmteil die Sendefuktion gerade ausgeführt 
wird.

9600 Baud oder so reichen übrigens locker.

lg PoWl

Autor: Paul H. (powl)
Datum:

Bewertung
0 lesenswert
nicht lesenswert
Die Daten der Shift-Variable müssen ja nun am TX-Pin ausgegeben werden. 
Wie shifte ich die das zu sendende Byte am besten und gebe das erste 
jeweils am TX-Pin aus?

In einer for-Schleife shiften und dann per if(bit_is_set(...)) ... das 
Bit am Port entweder setzen oder nicht, kann man das nicht auch 
irgendwie kopieren? Aus asm-Zeiten fällt mir da das Transfer-Bit ein.

lg PoWl

Autor: Stefan B. (stefan) Benutzerseite
Datum:

Bewertung
0 lesenswert
nicht lesenswert
Du willst also ein Software-UART machen und dabei ohne Timer-Interrupts 
arbeiten, d.h. deine Bitzeiten per Wartezyklen in Software machen, wenn 
ich dich richtig verstehe.

Man könnte dazu die Application Notes von Atmel befragen. Aber ich 
vermute, dass dort der Timer benutzt wird.

Hier eine Idee, wie es gehen könnte:
// Status: Hingeschrieben, nicht simuliert, nicht auf Hardware testet

// #define F_CPU  8000000L
#include <avr/io.h>
#include <util/delay.h>

// #1
#define BAUD          9600
#define SOFTUART_DDR  DDRD
#define SOFTUART_PORT PORTD
#define SOFTUART_TX   0         // Pin 0
#define SOFTUART_RX   1         // Pin 1

// 
// #3
// Zeitdauer für ein Bit bei 8N1, Baudrate BAUD 
// (1s) / (9600 Bit/s) = 104.17 µs
// 
#define SOFTUART_DELAY() delay_us(104.17)

void softuart_putc(unsigned char data)
{
  unsigned char i;

  // #2
  // 1 Startbit LOW
  SOFTUART_PORT &= ~(1 << SOFTUART_TX);
  SOFTUART_DELAY();

  // 8 Datenbits 
  for (i = 0; i < 8; i++)
  {
    if (data & 1) 
      SOFTUART_PORT |= (1 << SOFTUART_TX);
    else
      SOFTUART_PORT &= ~(1 << SOFTUART_TX);
    SOFTUART_DELAY();
    data = data >> 1;
  }

  // 1 Stopbit HIGH
  SOFTUART_PORT |= (1 << SOFTUART_TX);
  SOFTUART_DELAY();

  // Idle TTL HIGH
  SOFTUART_PORT |= (1 << SOFTUART_TX);
}

int main(void)
{
  // #1
  SOFTUART_DDR = (1 << SOFTUART_TX); // TX ist Ausgang, Rest Eingang

  while (1)
  {
    softuart_putc('*');
  }
}


#1 
http://www.mikrocontroller.net/articles/AVR-GCC-Tu...
#2 
http://www.mikrocontroller.net/articles/RS-232#Sig...
#3 http://de.wikipedia.org/wiki/EIA-232#Timing

Autor: Paul H. (powl)
Datum:

Bewertung
0 lesenswert
nicht lesenswert
Hi, danke, den Code und die Links schau ich mir morgen an. Folgendes 
habe ich nun rausgebracht. Mein PC kann das Zeug sogar empfangen, es 
funktioniert?!

Leider sendet er jedes Byte doppelt?! Die Funktion wird ja pro Sekunde 2 
mal aufgerufen und immer zweimal sendet er das gleiche (auf meinem 
Oszilloskop auch zu beobachten). Ausserdem sendet er nur bis 127 bzw. 
mein PC erkennt nicht mehr. Ob der wirklich 8 Bit sendet guck ich mir 
morgen an aber es sieht schon danach aus.
#include <avr/io.h>
#include <stdint.h>
#include <util/delay.h>

#define UART_DDR DDRA
#define UART_PORT PORTA
#define UART_TX PA1

#define UART_Baud 9600

void rs232_init(void)
{
  UART_DDR |= (1 << UART_TX);        // TX Pin als Ausgang schalten
  UART_PORT |= (1 << UART_TX);        // High-Aktiv
}

void rs232_send(unsigned char byte)
{
  UART_PORT &= ~(1 << UART_TX);        // Start-Bit

  _delay_loop_2(209);

  unsigned char x;

  for(x=0; x<8; x++)
  {
  if(bit_is_set(byte, 1))
  {
      UART_PORT |= (1 << UART_TX);      // High
  }
  else
  {
    UART_PORT &= ~(1 << UART_TX);      // Low
  }

    _delay_loop_2(209);

    byte >>= 1;                // Byte um 1 Bit nach rechts schieben
  }


  UART_PORT |= (1 << UART_TX);        // Parität / Stop-Bit + Delay

  _delay_loop_2(3 * 209);
}

int main(void)
{
  unsigned char counter;
  rs232_init();

  while(1)
  {
  rs232_send(counter++);

  _delay_ms(500);
  }
}

Übrigens kriege ich da eine Warnung:
../Laminatorsteuerung.c:48: warning: 'counter' may be used uninitialized 
in this function


damit ist unsigned char counter; gemeint. Wenn ich unsigned char counter 
= 0; hinschreibe geht es, der hexcode wird etwas länger. Aber 
funktionieren tut es genau gleich.

lg PoWl

Autor: STK500-Besitzer (Gast)
Datum:

Bewertung
0 lesenswert
nicht lesenswert
>warning: 'counter' may be used uninitialized in this function

in C werden Variablen nicht mit 0 initialisiert, sondern einfach so 
benutzt, wie sie im Speicher vorhanden sind.
Die Initialisierung muß der Programmierer übernehmen.

>Ausserdem sendet er nur bis 127 bzw. mein PC erkennt nicht mehr.

Das lässt irgendwie vermuten, dass du nur 7 Bits verschickst.

Autor: Rolf Magnus (Gast)
Datum:

Bewertung
0 lesenswert
nicht lesenswert
> Man könnte dazu die Application Notes von Atmel befragen. Aber ich
> vermute, dass dort der Timer benutzt wird.

Es gibt mehrere, darunter auch eine, die außer dem I/O-Port keine 
Peripherie braucht.

> in C werden Variablen nicht mit 0 initialisiert, sondern einfach so
> benutzt, wie sie im Speicher vorhanden sind.

Das gilt aber nur für nicht-statische lokale Variablen.

Die Initialisierung muß der Programmierer übernehmen.

>> Ausserdem sendet er nur bis 127 bzw. mein PC erkennt nicht mehr.
>
> Das lässt irgendwie vermuten, dass du nur 7 Bits verschickst.

Er sendet alles um ein Bit verschoben, da:
 if(bit_is_set(byte, 1))

Das sollte besser heißen:
 if(bit_is_set(byte, 0))

Autor: Paul H. (powl)
Datum:

Bewertung
0 lesenswert
nicht lesenswert
ok, aber die for-schleife läuft doch 8 mal durch? X beginnt bei 0, dann 
läuft die schleife durch, x wird hochgezählt. D.h. wenn x bei der 
nächsten Prüfung der schleifenbedingung 1 ist ist auch die schleife 
bereits einmal durchgelaufen. D.h. wenn x irgendwann 8 ist und die 
schleifenbedingung die schleife beendet ist die schleife auch schon 8 
mal durchgelaufen und es wurden 8 bit versendet, nicht?

Desweiteren kappier ich nicht wieso er die counter-variable nur alle 
zwei 500ms hochzählt?!

Rolf Magnus wrote:
>> Man könnte dazu die Application Notes von Atmel befragen. Aber ich
>> vermute, dass dort der Timer benutzt wird.
>
> Es gibt mehrere, darunter auch eine, die außer dem I/O-Port keine
> Peripherie braucht.

Die ist aber wie zu erwarten in asm geschrieben wo die Dauer der Befehle 
nicht vom Compiler abhängig ist, aber wenn gcc da immer den gleichen 
code liefert kann man das ja durchaus auch in C machen. Zufälligerweise 
funktioniert es ja auch so schon. Muss das ganze mal näher anschauen und 
abstimmen.

>
> Er sendet alles um ein Bit verschoben, da:
>
>
>  if(bit_is_set(byte, 1))
> 
>
> Das sollte besser heißen:
>
>
>  if(bit_is_set(byte, 0))
> 

Thx, ist mir garnicht aufgefallen :-) Probier ich nachher. Jetzt darf 
ich erstmal ne Englisch-Klausur schreiben -.-

lg PoWl

Autor: Rolf Magnus (Gast)
Datum:

Bewertung
0 lesenswert
nicht lesenswert
Ja. Da du allerdings nicht das unterste, sondern das zweite Bit von 
unten abfragst, ist innerhalb des Bytes alles um 1 Bit nach rechts 
verschoben. Dadurch kommt als oberstes Bit immer die 0, die 
nachgeschoben wird, heraus. Außerdem wird dadurch für zwei 
aufeinanderfolgende Bytes immer derselbe Wert gesendet, da das untere 
Bit ja rausfällt. Das erklärt auch, warum du jeden Wert zweimal siehst.

Autor: Paul H. (powl)
Datum:

Bewertung
0 lesenswert
nicht lesenswert
Ja, richtig, ist mir eben auch gekommen. So ich hab das ganze jetzt doch 
mal schnell geflasht. Funktioniert :-) Der PC empfängt alles richtig.

Heut Mittag beschäftige ich mich mal mit dem Timing, um näher ans 
Optimum ranzukommen. Vll läuft das ganze dann noch mit dem internen 
RC-Oszillator. Im Moment ist es erstmal wichtig damit ich einen 
Tempsensor kalibrieren kann :-)

lg PoWl

Autor: Stefan B. (stefan) Benutzerseite
Datum:

Bewertung
0 lesenswert
nicht lesenswert
Paul Hamacher wrote:
> Hi, danke, den Code und die Links schau ich mir morgen an.

Wenn das überhaupt noch nötig ist. Dann aber in meinem Posting noch 
einen Fehler korrigieren. In deinem Code ist das bereits richtig drin.

  // Idle TTL HIGH
  SOFTUART_PORT |= (1 << SOFTUART_TX);
}

int main(void)
{
  // #1
  SOFTUART_DDR = (1 << SOFTUART_TX);   // TX ist Ausgang, Rest Eingang
  SOFTUART_PORT |= (1 << SOFTUART_TX); // <=== Idle HIGH


Autor: Paul H. (powl)
Datum:

Bewertung
0 lesenswert
nicht lesenswert
So, das scheint ja nun schon zufällig ganz gut zu funktionieren. Gibt es 
aber noch eine Möglichkeit das ganze etwas genauer abzustimmen?

Dazu wäre erstmal die Vorraussetzung sicherzustellen, dass der GCC den 
Softuartcode immer gleich übersetzt und immer Register und nie 
irgendwann einfach SRAM für die Operationen darin nutzt, denn das würde 
ja viel länger dauern. Ansonsten wäre es ja notwendig das ganze direkt 
über ASM einzubinden.

Nicht, dass der Code hier nun zwar funktioniert, aber sich ein 
komplizierteres Programm dann auf einmal auf die kompilierung auswirkt.

lg PoWl

Autor: Stefan B. (stefan) Benutzerseite
Datum:

Bewertung
0 lesenswert
nicht lesenswert
Paul nicht böse sein, aber IMHO willst du eine Garantie auf etwas, bei 
dem dir keiner eine Garantie geben kann bzw. eine Garantie von GCC bzw. 
der dortigen Lizenz explizit ausgeschlossen ist.

Jeder mit etwas Background im Programmieren wird dir sagen:

Selbstverständlich ist es möglich, dass eine Codeänderung oder eine 
Weiterentwicklung der Toolchain zu Nebeneffekten führen kann.

Wenn du der Sache misstraust und es auf Leben und Tod darauf ankommt, 
darfst du halt (GC)C nicht einsetzen.

Dann machst du den Code selber in ASM und hoffst, dass die Hardware 
keinen Bug hat.

Oder du machst es mit einer Toolchain, bei der jemand den jeweiligen 
Produktionscode auf Herz und Nieren prüft und/oder den Kopf hinhält, 
wenn es schief geht.

Autor: Paul H. (powl)
Datum:

Bewertung
0 lesenswert
nicht lesenswert
Stefan "stefb" B. wrote:
> Paul nicht böse sein, aber IMHO willst du eine Garantie auf etwas, bei
> dem dir keiner eine Garantie geben kann bzw. eine Garantie von GCC bzw.
> der dortigen Lizenz explizit ausgeschlossen ist.

"Garantie", vor allem im Zusammenhang mit "Lizenz" sind viel zu harte 
Worte für so ein kleines Anliegen eines noch relativ unerfahrenen 
Hobbyprogrammierers der niemals im entferntesten an irgendeinen Anspruch 
auf eine Garantie gedacht hat ;-)

> Jeder mit etwas Background im Programmieren wird dir sagen:
>
> Selbstverständlich ist es möglich, dass eine Codeänderung oder eine
> Weiterentwicklung der Toolchain zu Nebeneffekten führen kann.

Na also, ich wollte nur freundlich fragen und das ist nun die passende 
Antwort darauf :-)

> Wenn du der Sache misstraust und es auf Leben und Tod darauf ankommt,
> darfst du halt (GC)C nicht einsetzen.

Keine Angst, es gibt keine Toten oder Verletzten.

> Dann machst du den Code selber in ASM und hoffst, dass die Hardware
> keinen Bug hat.

Wird wohl dann eventuell notwendig sein.

> Oder du machst es mit einer Toolchain, bei der jemand den jeweiligen
> Produktionscode auf Herz und Nieren prüft und/oder den Kopf hinhält,
> wenn es schief geht.

Wie gesagt befinden wir uns im absolut nicht-kommerziellen Bereich also 
besteht dafür auch keine Notwendigkeit, dass mir jemand dafür den Kopf 
hinhält ;-)

Danke für die Infos!

Neue Frage: Ich möchte den ADC auslesen. Wenn ich das über die 
ADC-Register mache
rs232_send_byte(ADCH);
rs232_send_byte(ADCL);

krig ich immernur 00000011 11111111 gesendet?

Wenn ich es folgendermaßen auslese funktionierts

rs232_send_byte(ADC >> 8);
rs232_send_byte(ADC);

lg PoWl

Autor: Rolf Magnus (Gast)
Datum:

Bewertung
0 lesenswert
nicht lesenswert
> Wenn ich das über die ADC-Register mache
>
> rs232_send_byte(ADCH);
> rs232_send_byte(ADCL);
>
> krig ich immernur 00000011 11111111 gesendet?

Weil du falschrum liest => Datenblatt konsultieren.

Autor: Peter Dannegger (peda)
Datum:

Bewertung
0 lesenswert
nicht lesenswert
Paul Hamacher wrote:
> Ist es irgendwie möglich in C eine kleine Routine zu schreiben die mir
> an einem Pin ein paar Bytes über Uart senden kann? Das ganze sollte ganz
> einfach ohne Timer funktionieren.

Woher kommt diese unnötige Angst vor Timern?

Wenn Du Delays benutzt, bist Du stark Compilereabhängig.
Außerdem werden Dir auch Interrupts das Timing versauen.

Mit einem Timer hast Du beide Nachteile nicht, wenn die Interrupts kurz 
gehalten sind.

Am einfachsten gehts mit einem Timerinterupt:

http://www.mikrocontroller.net/attachment/32137/sw_tx.zip


Peter

Antwort schreiben

Die Angabe einer E-Mail-Adresse ist freiwillig. Wenn Sie automatisch per E-Mail über Antworten auf Ihren Beitrag informiert werden möchten, melden Sie sich bitte an.

Wichtige Regeln - erst lesen, dann posten!

  • Groß- und Kleinschreibung verwenden
  • Längeren Sourcecode nicht im Text einfügen, sondern als Dateianhang

Formatierung (mehr Informationen...)

  • [c]C-Code[/c]
  • [avrasm]AVR-Assembler-Code[/avrasm]
  • [code]Code in anderen Sprachen, ASCII-Zeichnungen[/code]
  • [math]Formel in LaTeX-Syntax[/math]
  • [[Titel]] - Link zu Artikel
  • Verweis auf anderen Beitrag einfügen: Rechtsklick auf Beitragstitel,
    "Adresse kopieren", und in den Text einfügen




Bild automatisch verkleinern, falls nötig
Bitte das JPG-Format nur für Fotos und Scans verwenden!
Zeichnungen und Screenshots im PNG- oder
GIF-Format hochladen. Siehe Bildformate.
Hinweis: der ursprüngliche Beitrag ist mehr als 6 Monate alt.
Bitte hier nur auf die ursprüngliche Frage antworten,
für neue Fragen einen neuen Beitrag erstellen.

Mit dem Abschicken bestätigst du, die Nutzungsbedingungen anzuerkennen.