Serielle Schnittstelle für den ATtiny13  in C      

von Hermann Nieder                     
Elektronik-Labor  Projekte  AVR 

Lässt sich ein ATtiny13 in WinAVR trotz seines kleinen Flash-Speichers von 1kByte so programmieren, dass er mit einem PC über dessen serielle Schnittstelle kommunizieren kann, und zwar mit 9600 Baud?

Dies ist möglich, wie ich im Folgenden aufzeigen werde. Kürzlich entwarf ich  nämlich in Anlehnung an Burkhard Kainkas  Assembler-Unterprogramme zum  Empfangen und  Senden  für den  ATtiny13 auf der Platine des Lernpakets Mikrocontroller ein Programm in WinAVR, damit dieser Mikrocontroller, wie es in den ursprünglichen Programmen  in Assembler der Fall ist, über Pin PB1 senden und über PB2 empfangen kann.

Es entstand schließlich für den Kleinen, der über einen Flash-Speicher von nur 1 kByte verfügt, ein  „bescheidenes“ Interface-Programm, mit dem man  den Analog-Eingang ADC2 im 8-Bit-Modus einlesen kann. Außerdem lässt sich PortB.3 setzen bzw. wieder zurücksetzen.  Man kann auch ein Byte am PWM-Ausgang PB0 ausgeben lassen. Zusätzlich kann der Analog-Eingang ADC0  an Pin 1,  der auch als RESET-Eingang dient, abgefragt werden.

Allerdings wird anders als bei einem Assembler-Programm  für die vorliegende  Programmversion in WinAVR wesentlich mehr Programmspeicher, nämlich ca.68%,  benötigt.

Dies ist ein Auszug aus dem Programmlisting:

...
void main()
{
DDRB=0b00001011;// PB5: Eingang (ADC0, RESET) PB2: Eingang(RXD)
                // PB0:Ausgang, PB3: Ausgang, PB4: Eingang(ADC2) 
ADC_Init();
while(1)
{
A_Reg=0;
RdCOM();
A_Reg=E_Reg;
switch (A_Reg)
{
case 1:        // PWM
PWM_Init();
RdCOM();
OCR0A=E_Reg;
break;

case 2: 
ADMUX |= (1<<MUX1);  // MUX1 setzen
RdADC();  // ADC2 lesen
WrCOM();
break;

... 

Zum Senden an den PC wird die folgende Routine verwendet:   

 ...

void WrCOM()
{
PORTB |=(1<<PB1);    // TXD = 1
warte();
for (c=0;c<8;c++)
{
if((E_Reg & 1)==0)
{
PORTB |=(1<<PB1);    // TXD = 1  
}
else
{
PORTB &=~(1<<PB1);   // TXD = 0
}
warte();
E_Reg=E_Reg>>1;
}
PORTB &=~(1<<PB1);   // TXD = 0
warte();
}

 

WrCOM und RdCOM verwenden abwechselnd folgende Routine:...

void warte()
{
for (n=0;n<7;n++);  // 1 Bitlaenge
}

 

 

Auf einem kleinen Steckboard befinden sich wenige zusätzliche Bauteile, mit denen sich das oben erwähnte Interface-Programm für den Attiny13 erproben lässt.Die Reihenschaltung von R2 und R3, die Kollektor-Emitter-Strecke des Fototransistors T1 im Optokoppler sowie die Schaltung im Inneren des Mikrocontrollers sorgen dafür, dass die Spannung am Reset-Eingang nicht soweit sinkt, dass ein RESET ausgelöst wird. Wird der Eingang E des Optokopplers mit dem 5V-Anschluss verbunden, leuchtet LED1, und die Kollektor-Emitter-Strecke des Transistors T1 wird niederohmig. Die Spannung an ADC0 steigt von zuvor etwa 2,57 V auf ca. 4,75 V  an.

 

 

Die Software auf der CD zum Lernpaket Mikrocontroller beinhaltet ein Terminal-Programm, von dem hier ein Screenshot zu sehen ist.  Gerade wurde durch Senden einer 2 an den Mikrocontroller  dieser dazu veranlasst, ADC2 einzulesen und den Wert, im Bild 92,  zu senden. Danach wurde an den Attiny13 eine 5 gesendet, worauf dieser bei offenem Eingang E des Optokopplers 132 zurücksendete und anschließend bei erneutem Senden einer 5 den Wert 245 an  den PC übertrug, als der Eingang E des Optokopplers mit dem 5V-Anschluss verbunden war. Schließlich wurde durch Übertragung einer 1 und dem Wert 127 der PWM-Ausgang des Attiny13 angesteuert. Anschließend wurde PB3  zunächst mit einer 3 gesetzt und darauf mit einer 4 zurückgesetzt.

 

Die Werte der Bauteile der abgebildeten zusätzlichen Schaltung wurden so gewählt, dass diese  im Betrieb den Spannungsregler auf der Platine des Lernpakets Mikrocontroller nur minimal belasten. Die Stromstärke in der Zuleitung zum Steckboard beträgt weniger als 3 mA.

Download: T13_SER4.zip


Ergänzung von Ralf Beesner
Ich unternehme mal wieder einen Anlauf, C zu lernen, obwohl ich mich mit der perversen Syntax und der Fingerakrobatik (wegen geschweifter und eckiger Klammern) nicht wirklich anfreunden kann.
Da es unter C kein "eingebautes" Äquivalent zu den Bascom-Befehlen "PRINT #", "PUT #" und "GET #" gibt, bin ich auf Hermanns Soft-UART gestoßen.
Allerdings gab es bei mir zunächst nur Zeichenmüll. Ich mußte die Anzahl der Scheifendurchläufe in der Funktion "warte()" herabsetzen.
Die Ursache wird die verwendete Compiler-Version sein; während die meisten Leute GCC 4.3.6 verwenden (sprich: die aktuelle WinAVR-Version), ist bei mir Version 4.5.3 unter Linux im Einsatz.

Bei Assembler-Code werden Verzögerungssschleifen 1:1 umgesetzt; C-Compiler machen jedoch ihr eigenes Ding. Teilweise kann man das durch Compiler-Optionen beeinflussen (im Extremfall optimiert der Compiler Warteschleifen komplett weg).
Es wird daher allgemein empfohlen, Warteschleifen mit den Delay-Funktionen aus den AVR-Utils zu realisieren (dazu muss man sie zunächst mit "#include <util/delay.h>" oder "#include <util/delay_basic.h>" includieren).
Auch wenn man delay.h verwendet, ist die Compiler-Opimierungsstufe relevant. Ist die Optimierung komplett abgeschaltet (wie in Hermanns Projekt), ist lediglich die Verwendung von "delay_basic.h" möglich.

Bei der Verwendung der Delay-Funktionen gibt es noch ein Problem: In der Sende- und in der Empfangsschleife werden unterschiedliche Delays erforderlich, weil die Sende- und die Empfangsroutine unterschiedliche "Eigenzeit" verbrauchen. Bei 9600 bit/s fällt das bereits deutlich auf, bei 2400 bit/s ist es vernachlässigbar.

Ich habe daher im Ordner "O0" je drei geänderte Versionen beigefügt, mit denen man sich an die Problemlösung "herantasten" kann:
- die erste gibt nur eine Reihe von "U" aus (binär 1010101)
- die zweite einen längeren String
- die dritte "echot" Eingaben zurück; funktioniert das Senden mit den beiden vorgenannten Programmen einwandfrei, kann man mit ihr prüfen, wie die Zeichen im Mikrocontroller "ankommen"

Eine geeignetere Compiler-Option scheint übrigens "-Os" zu sein, die auf möglichst kompakten Code optimiert. Man kann dann delay.h includieren, statt "delay_loop_1()" "_delay_us()" verwenden und so statt der Anzahl der Schleifendurchläufe direkt eine Verzögerungszeit in µs angeben. Man muß dann allerdings in den Compileroptionen den korrekten CPU-Takt angeben, da daraus die Zeiten abgeleitet werden.
Bei kleinen Zeiten ist die Funktion nicht sehr genau, denn 1 µs sind bei 1,2 Mhz Takt nur 1,2 Takte.
Einige Beispiele mit der Option "-Os" sind ebenfalls beigefügt.

Sofern man einen Timer und einen Zählereingang (bzw. Pin Change Interrupt) übrig hat, sollte man die Wartezeiten damit realisieren. C-Beispielcode gibt es z.B. von Peter Danegger auf http://www.mikrocontroller.net .


 
Download: C-Soft-UART.zip

// softuart-echo-final.c

#include <avr/io.h>
#include <util/delay_basic.h>

unsigned char n,A_Reg,c,E_Reg;


void WrCOM(){
PORTB |=(1<<PB1); // TXD = 1
_delay_loop_1(27);
for (c=0;c<8;c++){
if((E_Reg & 1)==0){
PORTB |=(1<<PB1); // TXD = 1
}
else{
PORTB &=~(1<<PB1); // TXD = 0
}
_delay_loop_1(27);
E_Reg=E_Reg>>1;
}
PORTB &=~(1<<PB1); // TXD = 0
_delay_loop_1(27);
}


void RdCOM(){
while((PINB & 4)==0);
_delay_loop_1(45);
E_Reg=0;
for(c=0;c<8;c++){
E_Reg=E_Reg>>1;
if((PINB & 4)==4){
E_Reg=E_Reg|128;
}
_delay_loop_1(30);
}
_delay_loop_1(30);
E_Reg =~E_Reg;
}


void main(){

DDRB=0b00000010; // PB1:Ausgang
PORTB=0b00011010; // Pullups an ungenutzten Eingängen

//E_Reg = 85;
//WrCOM();

while(1){
RdCOM();
WrCOM();
}
}


Elektronik-Labor  Projekte  AVR