Forum: Mikrocontroller und Digitale Elektronik Minimal UART für Attiny gesucht


von eric (Gast)


Lesenswert?

Hallo,

ich hab hier einen Attiny85 den ich für Debuggingzwecke um einen 
minimalen UART erweitern möchte.

-Es reicht völlig wenn er nur senden kann, Empfang wird nicht benötigt.
-Beide Timer sind schon in Benutzung, Timer 1 läuft mit CTC und entweder 
OCR1A oder OCR1B wäre noch frei. Notfalls hätte der Teiler auch noch 
etwas Spiel.

Ich möchte natürlich, dass es den Rest des Programms möglichst wenig 
behindert.
Meine erste Idee war es mit der freien Leitung vom Timer das USI zu 
takten. Da mein Problem ja eigentlich nicht sonderlich selten ist, hat 
es mich überrascht, dass dazu kaum etwas zu finden war. Das meiste mit 
Senden und Empfangen und gleich ne Nummer größer als ich gehofft hab...

Sofern das mit dem USI überhaupt sinnvoll machbar ist, sollte das Timing 
softwareseitig kein Problem sein. Das wäre bei einem reinen software 
UART ja nicht unbedingt gegeben.

von Matthias S. (Firma: matzetronics) (mschoeldgen)


Lesenswert?

Wenn deine Timer schon einen OF Interrupt auslösen, kannst du einen 
davon 'huckepack' als seriellen Ausgang mitnutzen. Dazu brauchst du 
eigentlich nur einen 1-byte Buffer, indem dein zu sendendes Zeichen 
steht, einen Bitzähler und evtl. 2 Flags für 'neues Zeichen im Buffer' 
und 'Senden aktiv'.

von Jörg E. (jackfritt)


Lesenswert?

Per Bitbanging gehts evtl bis 9600.
Kommt auf dein restliches programm an.

von Peter D. (peda)


Lesenswert?

1
void sputchar( uint8_t c )
2
{
3
  c = ~c;
4
  STX_PORT &= ~(1<<STX_BIT);            // start bit
5
  for( uint8_t i = 10; i; i-- ){        // 10 bits
6
    _delay_us( 1e6 / BAUD );            // bit duration
7
    if( c & 1 )
8
      STX_PORT &= ~(1<<STX_BIT);        // data bit 0
9
    else
10
      STX_PORT |= 1<<STX_BIT;           // data bit 1 or stop bit
11
    c >>= 1;
12
  }
13
}

von Jörg E. (jackfritt)


Lesenswert?

Peters Code nutze ich auch ztw.
Danke dir nochmals ;)

Manchmal muss ich das Bit invertieren wenn MAX232 dranhängt.
Ich habe mit dem atiny85 mal ein paar Test mit internen 8Mhz gemacht.
eventuell klappts bei dir ja auch.
Hoffe es ist kein Fehler bei C&C passiert...
1
#include <util/delay.h>
2
#define TX_PORT PORTB
3
#define TX_PIN PB0
4
#define     TX_INVERT    // without MAX232
5
#define BAUD    9600UL
6
//Bitbanging
7
#define  BB            
8
9
// Das geht mit BB bei 8 Mhz
10
//#define BAUD    200UL
11
//#define BAUD    300UL
12
//#define BAUD    1200UL
13
#define BAUD    9600UL
14
//#define BAUD    4800UL
15
//#define BAUD    9600UL
16
//#define BAUD    19200UL
17
//#define BAUD    28800UL
18
//#define BAUD    38400UL
19
//#define BAUD    57600UL
20
21
// Ab hier nur mit Timer bei 8Mhz
22
//#define BAUD    76800UL
23
//#define BAUD    115200UL
24
25
// Ab hier nur mit externem Quartz
26
//#define BAUD    230400UL
27
//#define BAUD     256000UL
28
//#define BAUD    921600UL //C³ BT Modul
29
30
31
void tx(uint8_t c)
32
{
33
#ifdef BB
34
  c = ~c;
35
#ifndef TX_INVERT
36
  TX_PORT &= ~(1<<TX_PIN);            // start bit
37
#else
38
  TX_PORT |= (1<<TX_PIN);
39
#endif
40
  for( uint8_t i = 10; i; i-- ){        // 10 bits
41
    _delay_us( 1e6 / BAUD );
42
#ifndef TX_INVERT
43
    if( c & 1 )
44
      TX_PORT &= ~(1<<TX_PIN);        // data bit 0
45
    else
46
      TX_PORT |= (1<<TX_PIN);           // data bit 1 or stop bit
47
#else
48
    if( c & 1 )
49
      TX_PORT |= (1<<TX_PIN);        // data bit 0
50
    else
51
      TX_PORT &= ~(1<<TX_PIN);           // data bit 1 or stop bit
52
    c >>= 1;
53
#endif
54
  }
55
#else
56
    uint8_t i = tx_in;
57
58
    ROLLOVER( i, TX0_SIZE );
59
    tx_buff[tx_in] = ~c;      // complement for stop bit after data
60
    while( i == (*(volatile uint8_t*)&(tx_out)));    // until at least one byte free
61
    // tx_out modified by interrupt !
62
    tx_in = i;
63
#endif
64
  }

von c-hater (Gast)


Lesenswert?

eric schrieb:

> -Es reicht völlig wenn er nur senden kann, Empfang wird nicht benötigt.
> -Beide Timer sind schon in Benutzung, Timer 1 läuft mit CTC und entweder
> OCR1A oder OCR1B wäre noch frei. Notfalls hätte der Teiler auch noch
> etwas Spiel.
>
> Ich möchte natürlich, dass es den Rest des Programms möglichst wenig
> behindert.
> Meine erste Idee war es mit der freien Leitung vom Timer das USI zu
> takten.

Was für eine "freie Leitung" brauchst du denn? Das USI kann doch direkt 
von Timer0 getaktet werden. Nur völlig idiotische Timer-Nutzungsarten, 
die einen manuellen Reload des Zählregister beinhalten, können das 
verhindern...

> Sofern das mit dem USI überhaupt sinnvoll machbar ist, sollte das Timing
> softwareseitig kein Problem sein. Das wäre bei einem reinen software
> UART ja nicht unbedingt gegeben.

Wenn dein Timer0 mit einer "brauchbaren" Zyklusfrequenz läuft und du 
dich obendrein für die Debugausgaben auf einen 64 Zeichen großen 
Zeichensatz beschränkst (sinnvollerweise: ASCII32..96), dann hast du die 
optimale, minimalinvasive Debugausgabe.
Format 6N1. Die Ausgabe eines Zeichens (inklusive Prüfung, ob 
Schieberegister frei) kostet nur 8 MCU-Takte. Und die eigentliche 
Ausgabe läuft dann komplett mittels Hardware, ohne weitere Eingriffe 
durch die Software zu erfordern.

Besser geht's auf einem Tiny nicht (außer auf einem mit echter USART 
natürlich...)

von eric (Gast)


Lesenswert?

Peter: Als reine Verständnisfrage: der Code erzeugt 2 stop bits, oder?

c-hater schrieb:
> Was für eine "freie Leitung" brauchst du denn? Das USI kann doch direkt
> von Timer0 getaktet werden. Nur völlig idiotische Timer-Nutzungsarten,
> die einen manuellen Reload des Zählregister beinhalten, können das
> verhindern...

Damit meinte ich Timer/Counter0 Compare Match, oder geht das noch 
'direkter'?


c-hater schrieb:
> Format 6N1. Die Ausgabe eines Zeichens (inklusive Prüfung, ob
> Schieberegister frei) kostet nur 8 MCU-Takte. Und die eigentliche
> Ausgabe läuft dann komplett mittels Hardware, ohne weitere Eingriffe
> durch die Software zu erfordern.
>
> Besser geht's auf einem Tiny nicht (außer auf einem mit echter USART
> natürlich...)

Das klingt schon mal gut, mal gucken was mein Terminalprogramm bei 6N1 
ausspuckt.
Es sollte doch eigentlich auch möglich sein das mit dem Counter Overflow 
Interrupt auf 8 bit zu verlängern, aber 6 bit reichen.

von c-hater (Gast)


Lesenswert?

eric schrieb:

> Das klingt schon mal gut, mal gucken was mein Terminalprogramm bei 6N1
> ausspuckt.

Sichtbar? Nur Müll natürlich. Man muß zu jedem empfangenen Zeichen 32 
addieren, um wieder auf darstellbare ASCII-Zeichen zu kommen.

> Es sollte doch eigentlich auch möglich sein das mit dem Counter Overflow
> Interrupt auf 8 bit zu verlängern

Häh? Das Problem ist einfach, daß das verfügbare Schiebereister der 
Hardware einfach mal nur 8 Bit breit ist. Da man für einen UART-Betrieb 
Start- und Stopbit braucht, bleiben nur 6 Bit im Register für den 
Payload.

Die üblichen UART-Implementierungen für USI machen das anders, die 
erzeugen das Startbit i.d.R. komplett mittels Software und für das 
Stopbit nutzen sie entweder eine Eigenart des USI, die erstens nicht 
dokumentiert ist und die zweitens z.B. bei invertierter Ausgabe direkt 
für eine PC-UART auch nicht in dieser Form nutzbar ist oder sie erzeugen 
auch das Stopbit in Software. Damit ergeben sich in beiden Fällen 
Probleme beim Timing, die zwar natürlich lösbar sind, aber dem Ziel 
einer möglichst unabhängigen Debug-Ausgabe zuwieder laufen.

von Sebastian W. (wangnick)


Lesenswert?

eric schrieb:
> ich hab hier einen Attiny85 den ich für Debuggingzwecke um einen
> minimalen UART erweitern möchte.

Such mal nach tinydebugserial.

LG, Sebastian

von eric (Gast)


Lesenswert?

c-hater schrieb:
> Häh? Das Problem ist einfach, daß das verfügbare Schiebereister der
> Hardware einfach mal nur 8 Bit breit ist. Da man für einen UART-Betrieb
> Start- und Stopbit braucht, bleiben nur 6 Bit im Register für den
> Payload.

Er löst ja (wenn aktiviert) alle 8 bit einen Interrupt aus. Ob das 
reicht um die restlichen bits nahtlos anzufügen ist natürlich die andere 
frage.

Aber 6N1 reicht mir wie gesagt in dem fall, Und da ich sowieso fast 
ausschließlich Zahlen ausgebe, ist das mit dem Zeichensatz auch kein 
Ding.

von Petr Tomášek (Gast)


Lesenswert?

Hallo,

hier ist so ein UART, das ich mal gebastelt habe. Es läuft über den 
timer (war, glaube ich, für AT90USB647 geschrieben, aber könnte beim 
Anpassen auch bei ATtinys funktionieren)...

MfG
Petr

1
/*
2
 * (c) 2013 Petr Tomasek <petr.tomasek@evangnet.cz>
3
4
    This program is free software: you can redistribute it and/or modify
5
    it under the terms of the GNU General Public License as published by
6
    the Free Software Foundation, either version 3 of the License, or
7
    (at your option) any later version.
8
9
    This program is distributed in the hope that it will be useful,
10
    but WITHOUT ANY WARRANTY; without even the implied warranty of
11
    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
12
    GNU General Public License for more details.
13
14
    You should have received a copy of the GNU General Public License
15
    along with this program.  If not, see <http://www.gnu.org/licenses/>.
16
17
 */
18
19
#include <avr/pgmspace.h>
20
#include <avr/interrupt.h>
21
#include <avr/sleep.h>
22
23
#include "utils.h"
24
25
volatile unsigned char dbg_serial_buffer[256];
26
volatile unsigned char dbg_serial_start; // interrupt reads from here...
27
volatile unsigned char dbg_serial_end;   // user writes here...
28
volatile unsigned char dbg_serial_bit;
29
30
31
/* --------------- interrupt ------------------- */
32
33
ISR(TIMER1_COMPA_vect) {
34
 if (!dbg_serial_bit)
35
 {
36
   if (dbg_serial_end!=dbg_serial_start) // TODO: CTS !!!
37
   {
38
     // Start bit
39
     io_clear(DBG_SERIAL_TX); 
40
     dbg_serial_bit++;
41
   }
42
 }
43
 else if (dbg_serial_bit<=8)
44
 {
45
   // one bit
46
   if (dbg_serial_buffer[dbg_serial_start]&1)
47
     io_set(DBG_SERIAL_TX); 
48
   else
49
     io_clear(DBG_SERIAL_TX); 
50
51
   dbg_serial_buffer[dbg_serial_start]/=2;
52
   dbg_serial_bit++;
53
 }
54
 else
55
 {
56
   // Stop bit
57
   io_set(DBG_SERIAL_TX); 
58
   dbg_serial_start++;
59
   dbg_serial_bit=0;
60
 }
61
}
62
63
64
/* --------------------- init ------------------- */
65
66
void dbg_serial_init (void) {
67
  TCCR1A = 0x00;                    // NO OC* pins + WGM11:01<=00 => CTC mode
68
  TCCR1B = (1<<WGM12) | (1<<CS10);  //  WGM13:02<=01  => CTC mode,  CS12:10<= 001 == Fclk
69
70
  OCR1A = (unsigned int)(F_CPU/DBG_SERIAL_BAUD_RATE+0.5);
71
72
#ifdef DBG_SERIAL_CTS
73
  ddr_set_in(DBG_SERIAL_TX); 
74
#endif
75
  ddr_set_out(DBG_SERIAL_TX); 
76
  io_set(DBG_SERIAL_TX); 
77
78
  // interrupt
79
  TIMSK1 = (1<<OCIE1A);
80
  sei();
81
82
}
83
84
85
/* --------------------- user ------------------- */
86
87
void dbg_serial_printchar (char ch) {
88
89
  while (dbg_serial_start==dbg_serial_end+1)
90
  {
91
    sleep_cpu(); 
92
  }
93
  dbg_serial_buffer[dbg_serial_end++]=ch;
94
}
95
96
signed char dbg_serial_printchar_nonblock (char ch) {
97
98
  if (dbg_serial_start==dbg_serial_end+1) return -1;
99
  dbg_serial_buffer[dbg_serial_start++];
100
  return 0;
101
}
102
103
void dbg_serial_print_p (const char * s) {
104
unsigned char ch;
105
while ((ch=pgm_read_byte(s))) {
106
 dbg_serial_printchar(ch);
107
 s++;
108
 }
109
}
110
111
// TODO: atomic: either fail or print everything!
112
signed char dbg_serial_print_p_nonblock (const char * s) {
113
unsigned char ch;
114
while ((ch=pgm_read_byte(s))) {
115
 if (dbg_serial_printchar_nonblock(ch)) return -1;
116
 s++;
117
 }
118
 return 0;
119
}
120
121
#define dbg_serial_print_P(__s)  dbg_serial_print_p(PSTR(__s))
122
#define dbg_serial_print_P_nonblock(__s)  dbg_serial_print_p_nonblock(PSTR(__s))
123
124
void dbg_serial_print_maxlen (char * s, unsigned int maxl) {
125
unsigned char ch;
126
//unsigned int ii;
127
//while ((ch=*s) && (ii<maxl)) {
128
while ((ch=*s)) {
129
 dbg_serial_printchar(ch);
130
 s++; //ii++;
131
 }
132
}
133
134
void dbg_serial_printciff (unsigned char cf) {
135
 if (cf>9) dbg_serial_printchar (cf+'a'-10);
136
 else dbg_serial_printchar (cf+'0');
137
 return;
138
}
139
140
void dbg_serial_printx (unsigned char x) {
141
 dbg_serial_printciff(x/16);
142
 dbg_serial_printciff(x%16);
143
 return;
144
}
145
146
void dbg_serial_print010 (unsigned char c) {
147
 unsigned char cc;
148
 dbg_serial_printciff(c/100);
149
 cc=c%100;
150
 dbg_serial_printciff(cc/10);
151
 dbg_serial_printciff(cc%10);
152
 return;
153
}
154
155
void dbg_serial_print10 (unsigned char c) {
156
 unsigned char cc;
157
 if (c>99) dbg_serial_printciff(c/100);
158
 cc=c%100;
159
 if (c>9) dbg_serial_printciff(cc/10);
160
 dbg_serial_printciff(cc%10);
161
 return;
162
}

von eric (Gast)


Lesenswert?

Ich hab mich mal an dem USI versucht, leider ohne wirklichen Erfolg.
Die Dokumentation zum USI ist stellenweise leider ungenau oder 
unvollständig, sodass einiges nur durch Rumprobieren rauszufinden war.

Mit dem angefügten Code klappt es immerhin schon mal, den Pin konstant 
auf High zu halten...

Sobald ich versuche etwas anderes zu senden, kommt zwar etwas an, aber 
er scheint Probleme zu haben Anfang und Ende richtig zu erkennen. Das 
delay in der ISR sollte jetzt sicherstellen, dass auch für das Stopbit 
das Timing eingehalten wird, hat aber nicht den gewünschten Erfolg und 
ist hässlich.

F_CPU ist 1MHz und der Code von Peter funktioniert soweit. Ein 
Oszilloskop um mir das Signal mal genauer anzugucken hab ich leider 
nicht...
USI ist jedenfalls ein ziemlicher Krampf, da bin ich vermutlich besser 
dran im Timer Interrupt selber am Pin zu wackeln...
1
#include <avr/interrupt.h>
2
#include <avr/io.h> 
3
#include <util/delay.h>
4
#include <stdint.h>
5
6
#define BAUD 1200UL
7
8
void initTimer(void) {
9
  TCCR0A =
10
    (1 << WGM01) | // CTC
11
    (0 << WGM00);
12
13
  TCCR0B =
14
    (0 << WGM02) |
15
    (0 << CS02)  | // clk/8
16
    (1 << CS01)  |
17
    (0 << CS00);
18
19
  OCR0A = (unsigned int)(F_CPU/(BAUD*8UL)+0.5);
20
}
21
22
void initUSI(void) {
23
  USIDR = 0xFF;
24
25
  USICR =
26
    (1<<USIOIE) |
27
    (0<<USIWM1) | // 3 wire mode
28
    (1<<USIWM0) |
29
    (0<<USICS1) | // Timer/Counter0 Compare Match
30
    (1<<USICS0);
31
}
32
33
ISR(USI_OVF_vect) {
34
  //_delay_us(1e6 / BAUD);
35
  USIDR=0xFF;
36
37
  TCNT0=0;
38
  USISR = (1<<USICNT3) | (1<<USICNT0); // USICNT = 9
39
  USISR |= (1<<USIOIF);
40
}
41
42
int main(void) {
43
  initTimer();
44
  initUSI();
45
46
  DDRB |= (1<<DDB1); // UART
47
48
  sei();
49
50
  for(;;);
51
52
  return 0;
53
}

von Peter D. (peda)


Lesenswert?

eric schrieb:
> USI ist jedenfalls ein ziemlicher Krampf

Ja, was sich Atmel dabei gedacht hat, ist mir ein Rätsel. Es ist 
wirklich zu nichts nütze.

von eric (Gast)


Lesenswert?

Mit dem Timer Interrupt hat es jetzt ~10 Minuten gedauert und es kommt 
sogar auf Anhieb das an was ankommen soll... autsch...

von Verwirrter Anfänger (Gast)


Lesenswert?

Ich häng hier einfach mal meine kleine bit banging Variante dran. Das 
ist im Grunde das Gleiche wie von Peter oben, nur etwas mehr als Library 
aufgemacht.


header:
1
#define __UART_H
2
#include <stdint.h>
3
#include <avr/io.h>
4
 
5
// Baud rate
6
#define UART_BPS 9800
7
#define UART_TX_DDR  DDRB
8
#define UART_TX_PORT PORTB
9
#define UART_TX_PIN  PB0
10
 
11
extern uint8_t uart_setup(void);
12
extern void uart_putc(char c);
13
extern void uart_puts(char* s);
14
 
15
#endif


code:
1
#include <stdint.h>
2
#include <avr/io.h>
3
#include <util/delay.h>
4
#include "uart.h"
5
 
6
#define UART_SET_BIT(port,bit)   (port |=  (1 << bit))
7
#define UART_CLEAR_BIT(port,bit) (port &= ~(1 << bit))
8
 
9
#define UART_BIT_WAIT()  _delay_us(1000000.0 / (float) UART_BPS)
10
#define UART_SEND_HIGH() {UART_CLEAR_BIT(UART_TX_PORT, UART_TX_PIN); UART_BIT_WAIT();}
11
#define UART_SEND_LOW()  {UART_SET_BIT(UART_TX_PORT, UART_TX_PIN); UART_BIT_WAIT();}
12
 
13
uint8_t uart_setup(void){
14
  UART_SET_BIT(UART_TX_DDR, UART_TX_PIN);
15
  UART_SET_BIT(UART_TX_PORT, UART_TX_PIN);
16
  return 0;
17
}
18
 
19
void uart_putc(char c){
20
  uint8_t i = 8;
21
  // start bit
22
  UART_SEND_HIGH();
23
  while(i){
24
    if(c & 1){
25
      UART_SEND_LOW();
26
    } else {
27
      UART_SEND_HIGH();
28
    }
29
    c = c >> 1;
30
    i--;
31
  }
32
  // stop bits
33
  UART_SEND_LOW();
34
}
35
 
36
void uart_puts(char* s){
37
  while (*s) {
38
    uart_putc(*s);
39
    s++;
40
  }
41
}

von c-hater (Gast)


Lesenswert?

Peter Dannegger schrieb:

> eric schrieb:
>> USI ist jedenfalls ein ziemlicher Krampf

Das ja.

> Ja, was sich Atmel dabei gedacht hat, ist mir ein Rätsel. Es ist
> wirklich zu nichts nütze.

Das ist allerdings Unsinn. Ja klar, ein Stück Hardware, was wirklich das 
Akronym "USI" verdient, hätte schon gern ein ganzes Stück universeller 
sein dürfen als das USI tatsächlich ist, soweit stimme ich dir da 
vollkommen zu.

Das heißt aber andererseits nun auch ganz sicher nicht, daß das USI 
wirklich zu garnix nützlichem zu gebrauchen wäre. Das ist Quatsch. 
Insbesondere ist es Quatsch im Bezug auf das konkrete Thema dieses 
Threads.

Man kann damit eine "minimalinvasive" Debugausgabe realisieren, die 
praktisch unabhängig von jeglichem Timing der eigentlichen Anwendung ist 
(und auch umgekehrt die Anwendung nur minimal tangiert). Das kostet USI 
und leider auch zumindest Restriktionen für die Benutzung von Timer0 
durch die Anwendung. Aber das war ja von vornherein klar.

Ergänzend: Ich war in meinen Postings in diesem Thread bisher davon 
ausgegangen, daß die Timing-Unabhängigkeit der Ausgabe nur mit 6N1 
möglich wäre.
Inzwischen habe ich mir die Sache nochmal genauer angeschaut und 
festgestellt, daß auch 7N1 rein in Hardware geht. Das macht die 
Debug-Ausgabe auf diesem Weg praktisch vollwertig (weil dann mit jedem 
normalen Terminalprogramm nutzbar). Ein weiterer Vorteil bei der 
7N1-Betriebsart ist, daß die eigentliche Debug-Ausgabe hier nicht 8, 
sondern nur 6 MCU-Takte kostet, der Einfluß der Debug-ausgabe auf die 
Anwendung als noch geringer ist.

Der Nachteil: zusätzlich zu dem naturgemäß immer benutzten DO fällt dann 
auch noch der mit DI verknispelte µC-Pin für die Benutzung durch die 
eigentliche Anwendung aus, was insbesondere bei den Tinys mit ganz 
wenigen Pins durchaus eine herbe Einschränkung sein kann.

Naja, und ganz zuletzt ist mir auch noch aufgefallen, daß auch 8N1 rein 
in Hardware möglich wäre, das allerdings nur, wenn Timer0 wirklich 
exklusiv für die Debugausgabe genutzt wird (6N1 und 7N1 lassen vielfach 
eine Mitnutzung durch die Anwendung zu, die wäre zwar auch bei 8N1 nicht 
völlig ausgeschlossen, würde aber das Timing der Anwendung beeinflussen, 
weil an TCNT geschraubt werden muß, um das Startbit jederzeit passend 
generieren zu können)

von Armin J. (arminj)


Lesenswert?

Für einen 1 Mhz Attiny hatte ich auch mal das Problem mit der 
Debuggausgabe und hab dann Peters Idee aufgegriffen und bin erfolgreich 
bis 38400 Baud gekommen.
Der komplette Code ist auf Github 
https://github.com/ArminJo/AttinySendSerial_1Mhz_38400Bd
1
/*
2
 * Only multiple of 4 cycles are possible. Last loop is only 3 cycles.
3
 * 3 -> 11 cycles
4
 * 4 -> 15 cycles
5
 */
6
inline void delay4MicrosecondsInlineExact(uint16_t a4Microseconds) {
7
    // the following loop takes 4 cycles (4 microseconds  at 1MHz) per iteration
8
    __asm__ __volatile__ (
9
            "1: sbiw %0,1" "\n\t"    // 2 cycles
10
            "brne .-4" : "=w" (a4Microseconds) : "0" (a4Microseconds)// 2 cycles
11
    );
12
}
13
14
/*
15
 * 26 cycles per bit, 260 per byte for 38400 baud
16
 * 24 cycles between each cbi or sbi command
17
 */
18
void write1Start8Data1StopNoParity(uint8_t aChar) {
19
    // start bit
20
    PORTB &= ~(1 << TX_PIN);
21
    _NOP();
22
    delay4MicrosecondsInlineExact(4);
23
24
    // 8 data bits
25
    uint8_t i = 8;
26
    do {
27
        if (aChar & 0x01) {
28
            // bit=1
29
            // to compensate for jump at data=0
30
            _NOP();
31
            PORTB |= 1 << TX_PIN;
32
        } else {
33
            // bit=0
34
            PORTB &= ~(1 << TX_PIN);
35
            // compensate for different cycles of sbrs
36
            _NOP();
37
            _NOP();
38
        }
39
        aChar = aChar >> 1;
40
        // 3 cycles padding
41
        _NOP();
42
        _NOP();
43
        _NOP();
44
        delay4MicrosecondsInlineExact(3);
45
        --i;
46
    } while (i > 0);
47
48
    // to compensate for missing loop cycles at last bit
49
    _NOP();
50
    _NOP();
51
    _NOP();
52
    _NOP();
53
54
    // Stop bit
55
    PORTB |= 1 << TX_PIN;
56
    // -8 cycles to compensate for fastest repeated call (1 ret + 1 load + 1 call)
57
    delay4MicrosecondsInlineExact(4); // gives minimum 25 cycles for stop bit :-)
58
}

von Stefan F. (Gast)


Lesenswert?

>> USI ist jedenfalls ein ziemlicher Krampf
> Ja, was sich Atmel dabei gedacht hat, ist mir ein Rätsel. Es ist
> wirklich zu nichts nütze.

Ich habe damit mal eine I2C Slave implementiert und dabei soviel Frust, 
dass ich beinahe auf eine reine Softwarelösung umgestiegen wäre.

von Stefan F. (Gast)


Lesenswert?

Ich habe auch noch eine Variante, um die bereits üppige Sammlung zu 
erweitern :-)
1
// Required setting, usually in the Makefile:
2
// #define F_CPU 1200000
3
4
#define SOFTSERIAL_BITRATE    2400
5
#define SOFTSERIAL_TXD_INIT   { DDRB  |= (1<<PB3);  }
6
#define SOFTSERIAL_TXD_HIGH   { PORTB |= (1<<PB3);  }
7
#define SOFTSERIAL_TXD_LOW    { PORTB &= ~(1<<PB3); }
8
9
// Files for input and output through serial port.
10
static int serial_write(char, FILE *);
11
static FILE serialPort = FDEV_SETUP_STREAM(serial_write, 0, _FDEV_SETUP_WRITE);
12
13
// Calculate the time per bit minus 5 CPU cycles
14
#define US_PER_BIT 1000000/SOFTSERIAL_BITRATE-5/F_CPU
15
16
#if F_CPU/SOFTSERIAL_BITRATE<100
17
    #warning SOFTSERIAL_BITRATE is to high for this CPU clock frequency
18
#endif
19
20
// Initialize the serial port
21
void initSerialConsole(void) 
22
{
23
    SOFTSERIAL_TXD_INIT;
24
    SOFTSERIAL_TXD_HIGH;
25
    // Bind stdout to the serial port
26
    stdout = &serialPort;
27
}
28
29
30
// Write a character to the serial port
31
static int serial_write(char c, FILE *f) 
32
{
33
    cli();
34
    // Send start bit
35
    SOFTSERIAL_TXD_LOW;
36
    _delay_us(US_PER_BIT);
37
    // Send 8 data bits
38
    for (uint8_t i=0; i<8; i++) 
39
    {
40
        if (c & 1)
41
        {
42
            SOFTSERIAL_TXD_HIGH;
43
        }
44
        else
45
        {
46
            SOFTSERIAL_TXD_LOW;
47
        }
48
        _delay_us(US_PER_BIT);
49
        c=c>>1;
50
    }
51
    SOFTSERIAL_TXD_HIGH;  
52
    sei();
53
    _delay_us(2*US_PER_BIT);
54
    return 0;
55
}


Du musst zuerst initSerialConsole() aufrufen. Danach kannst du mit den 
Funktionen der Standard C Library Texte senden, zum Beispiel puts() und 
printf().

von Stefan F. (Gast)


Lesenswert?

@Verwirrter Anfänger:
1
  UART_SEND_HIGH();
2
  while(i){
3
    if(c & 1){
4
      UART_SEND_LOW();
5
    } else {
6
      UART_SEND_HIGH();
7
    }

Kann es sein, das du High/Low vertauscht hast?

von Stefan F. (Gast)


Lesenswert?

> Per Bitbanging gehts evtl bis 9600.

Da geht locker flockig noch viel mehr. Senden ist einfach.

von Jörg E. (jackfritt)


Lesenswert?

@Stefan
Sieht man ja im Code danach in meinen Kommentaren.
Hatte ich mal mit rumprobiert.
Aber jeder Tiny ohne Quarz war halt ab 57600 anders bzw. "fehlerhafter"

Is halt die Frage ob ich für Debugging so schnell sein muss, wenns mal 
nen Variable is.

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.