Source file: /~heha/basteln/PC/FunkUsb/dcf77franz9.zip/serial.cpp

#include "dcf77.h"
#include <avr/io.h>
#include <avr/interrupt.h>
#include <stdarg.h>	// va_list
#include <math.h>	// fabs

/*
240704	habe ich  die serielle „Debug-Ausgabe“ auf USI umgestellt.
Diese lässt sich mit Timer0-CompareA jitterfrei steuern,
und das erlaubt ordentlich hohe Baudraten.
Damit hat Timer0 eine Doppelaufgabe (ADC und USI),
und Timer1 wird für die Quarzuhr frei.

Zur Mindestlänge des Stoppbits kommt es _vor_ dem Generieren des Startbits.
Das Ausgabeport PB1 musste gar nicht geändert werden.
PB0 ginge auch, aber da braucht es noch einen externen Pullup.

240705	Statt mit der USI wäre auch die Ausgabe mittels Compare-Output
genauso jitterfrei möglich. An PB0 ist es OC0A, an PB1 ist es OC0B.
In TCCR0A wird dazu Bit 6 für PB0 bzw. Bit 4 für OC0B geladen.

Die USI hilft beim jitterarmen UART-Empfang aus der Patsche:
Die Startbiterkennung ist leider niemals jitterfrei mangels Capture,
die restlichen Bitabtastzeitpunkte sind untereinander exakt aber
variabel zum Startbit-Jitter.
*/

static constexpr byte UART_OcrStep() {
 constexpr double v = (float)F_CPU/TIM_Prescaler/UART::Baud;
 constexpr unsigned u = unsigned(v+0.5);
 static_assert(v<256,"Baudrate zu klein für Timer0-Vorteiler");
 static_assert(fabs(u-v)/v<=0.03,"Erreichbare Baudrate weicht mehr als 3 % von Zielvorgabe ab");
// gebrochener Teiler hilft dann, aber das erscheint mir erst mal als Overkill
 return byte(v);
}

// Mit jedem Schiebetakt den Compare-Wert vorrücken
// und das nächste auszuschiebende Bit im Bit 6 von USIDR platzieren
// (Das Bit 7 von USIDR ist das gerade „außen sichtbare“ Bit.)
ISR(TIM0_COMPA_vect,ISR_NAKED) {
 asm(
"	mov	r2,r24		\n"
"	in	r3,%0		\n"
"	in	r24,%1		\n"	// OCR0A
"	subi	r24,-%2		\n"	// vorrücken bevor es zu spät ist
"	out	%1,r24		\n"	// OCR0A
"	sbic	%3,0		\n"	// Am ATtiny ist GPIOR1 bitadressierbar
"	 sbi	0x0F,6		\n"	// Bit kopieren bevor die nächste Taktflanke zuschlägt
"	sbis	%3,0		\n"
"	 cbi	0x0F,6		\n"
"	in	r24,%3		\n"	// GPIOR1
"	sec			\n"	// Stoppbit einschieben
"	ror	r24		\n"	// Datenbit ausschieben
"	out	%3,r24		\n"	// GPIOR1 rückschreiben
"	sbis	0x0E,6		\n"	// USISR: Zählerüberlauf?
"	 rjmp	9f		\n"
"	cbi	0x0D,2		\n"	// USICR: Taktung beenden: Stoppbit bleibt stehen
"	in	r24,0x39	\n"	// TIMSK
"	cbr	r24,1<<4	\n"	// OCR0A-Interrupts beenden
"	out	0x39,r24	\n"	// TIMSK
"9:	out	%0,r3		\n"
"	mov	r24,r2		\n"
"	reti			\n"
::	"I"(_SFR_IO_ADDR(SREG)),
	"I"(_SFR_IO_ADDR(OCR0A)),
	"X"(UART_OcrStep()),
	"I"(_SFR_IO_ADDR(GPIOR1))
 );
}

void UART::Init() {
 USIDR |= 0x80;		// DO auf H-Pegel
 USICR |= 0x10;		// DO an PB1 anlegen (Dreidrahtmodus)
}

static bool UART_IsBusy() {
 return !!(USICR&4);	// USI-Taktung aktiv?
}

void UART::Send(byte c) {
 while (UART_IsBusy()) idle();
 GPIOR1 = c;
 USISR = 0xE6;		// IRQ nach 10 Bits, Interruptflags löschen
 OCR0A = TCNT0+UART_OcrStep();
 TIFR  = 1<<OCF0A;	// IRQ der ständig unbedienten Compares löschen
 TIMSK |= 1<<OCIE0A;	// gewährt Mindeststoppbitzeit
 USIDR &=~(1<<6);	// Startbit im Bit 6 platzieren
 USICR |= 1<<2;		// Taktung starten
}

/* Taktschema:
Takt	Ausgang	Zähler
-	Hi	6	UART::Send setzt OCR0A und aktiviert Interrupt sowie Taktung
1	Lo	7	ISR setzt D6 auf LSB = 	D0
2	D0	8				D1
3	D1	9				D2
4	D2	10				D3
5	D3	11				D4
6	D4	12				D5
7	D5	13				D6
8	D6	14				D7
9	D7	15				1
10	Hi	0	ISR deaktiviert Interrupt und Taktung, Timer0 läuft weiter
*/

// uprintf => Vollständig in Assembler. Benötigt eine Callback-Routine „chrout“.
// Diese darf Z sowie W18 nicht verändern. (W16 und Y sowieso nicht.)
extern "C" void chrout(char c) {
 asm(
"	push	r18	\n"
"	push	r19	\n"
"	push	r30	\n"
"	push	r31	\n"
"	rcall	_ZN4UART4SendEh	\n"
"	pop	r31	\n"
"	pop	r30	\n"
"	pop	r19	\n"
"	pop	r18	\n");
}
Detected encoding: UTF-80