Forum: Compiler & IDEs UART senden ohne Timer emulieren


von Paul H. (powl)


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

von Paul H. (powl)


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

von Stefan B. (stefan) Benutzerseite


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:
1
// Status: Hingeschrieben, nicht simuliert, nicht auf Hardware testet
2
3
// #define F_CPU  8000000L
4
#include <avr/io.h>
5
#include <util/delay.h>
6
7
// #1
8
#define BAUD          9600
9
#define SOFTUART_DDR  DDRD
10
#define SOFTUART_PORT PORTD
11
#define SOFTUART_TX   0         // Pin 0
12
#define SOFTUART_RX   1         // Pin 1
13
14
// 
15
// #3
16
// Zeitdauer für ein Bit bei 8N1, Baudrate BAUD 
17
// (1s) / (9600 Bit/s) = 104.17 µs
18
// 
19
#define SOFTUART_DELAY() delay_us(104.17)
20
21
void softuart_putc(unsigned char data)
22
{
23
  unsigned char i;
24
25
  // #2
26
  // 1 Startbit LOW
27
  SOFTUART_PORT &= ~(1 << SOFTUART_TX);
28
  SOFTUART_DELAY();
29
30
  // 8 Datenbits 
31
  for (i = 0; i < 8; i++)
32
  {
33
    if (data & 1) 
34
      SOFTUART_PORT |= (1 << SOFTUART_TX);
35
    else
36
      SOFTUART_PORT &= ~(1 << SOFTUART_TX);
37
    SOFTUART_DELAY();
38
    data = data >> 1;
39
  }
40
41
  // 1 Stopbit HIGH
42
  SOFTUART_PORT |= (1 << SOFTUART_TX);
43
  SOFTUART_DELAY();
44
45
  // Idle TTL HIGH
46
  SOFTUART_PORT |= (1 << SOFTUART_TX);
47
}
48
49
int main(void)
50
{
51
  // #1
52
  SOFTUART_DDR = (1 << SOFTUART_TX); // TX ist Ausgang, Rest Eingang
53
54
  while (1)
55
  {
56
    softuart_putc('*');
57
  }
58
}

#1 
http://www.mikrocontroller.net/articles/AVR-GCC-Tutorial#Allgemeiner_Zugriff_auf_Register
#2 
http://www.mikrocontroller.net/articles/RS-232#Signalpegel.2C_Spannungsversorgung
#3 http://de.wikipedia.org/wiki/EIA-232#Timing

von Paul H. (powl)


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.
1
#include <avr/io.h>
2
#include <stdint.h>
3
#include <util/delay.h>
4
5
#define UART_DDR DDRA
6
#define UART_PORT PORTA
7
#define UART_TX PA1
8
9
#define UART_Baud 9600
10
11
void rs232_init(void)
12
{
13
  UART_DDR |= (1 << UART_TX);        // TX Pin als Ausgang schalten
14
  UART_PORT |= (1 << UART_TX);        // High-Aktiv
15
}
16
17
void rs232_send(unsigned char byte)
18
{
19
  UART_PORT &= ~(1 << UART_TX);        // Start-Bit
20
21
  _delay_loop_2(209);
22
23
  unsigned char x;
24
25
  for(x=0; x<8; x++)
26
  {
27
  if(bit_is_set(byte, 1))
28
  {
29
      UART_PORT |= (1 << UART_TX);      // High
30
  }
31
  else
32
  {
33
    UART_PORT &= ~(1 << UART_TX);      // Low
34
  }
35
36
    _delay_loop_2(209);
37
38
    byte >>= 1;                // Byte um 1 Bit nach rechts schieben
39
  }
40
41
42
  UART_PORT |= (1 << UART_TX);        // Parität / Stop-Bit + Delay
43
44
  _delay_loop_2(3 * 209);
45
}
46
47
int main(void)
48
{
49
  unsigned char counter;
50
  rs232_init();
51
52
  while(1)
53
  {
54
  rs232_send(counter++);
55
56
  _delay_ms(500);
57
  }
58
}

Ü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

von STK500-Besitzer (Gast)


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.

von Rolf Magnus (Gast)


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:
1
 if(bit_is_set(byte, 1))

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

von Paul H. (powl)


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:
>
>
1
>  if(bit_is_set(byte, 1))
2
>
>
> Das sollte besser heißen:
>
>
1
>  if(bit_is_set(byte, 0))
2
>

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

lg PoWl

von Rolf Magnus (Gast)


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.

von Paul H. (powl)


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

von Stefan B. (stefan) Benutzerseite


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.
1
  // Idle TTL HIGH
2
  SOFTUART_PORT |= (1 << SOFTUART_TX);
3
}
4
5
int main(void)
6
{
7
  // #1
8
  SOFTUART_DDR = (1 << SOFTUART_TX);   // TX ist Ausgang, Rest Eingang
9
  SOFTUART_PORT |= (1 << SOFTUART_TX); // <=== Idle HIGH

von Paul H. (powl)


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

von Stefan B. (stefan) Benutzerseite


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.

von Paul H. (powl)


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
1
rs232_send_byte(ADCH);
2
rs232_send_byte(ADCL);

krig ich immernur 00000011 11111111 gesendet?

Wenn ich es folgendermaßen auslese funktionierts

1
rs232_send_byte(ADC >> 8);
2
rs232_send_byte(ADC);

lg PoWl

von Rolf Magnus (Gast)


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.

von Peter D. (peda)


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

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.