Source file: /~heha/Mikrocontroller/RpiI2cClockStretcher/i2c.zip/i2c.cpp

/* Firmware für den ATmega32U4
   Zuständig: Felix Pfeiffer
   Henrik Haftmann
 230605	erstellt
+211028	Anpassung an silberne Ofensteuerung
Aufgaben:
1. USB-Interface für WebUSB, hier mal ohne Bulk-Pipes, nur EP0
2. I²C-Master 400 kHz

Hardware:
Pro Micro, idealerweise als 3,3-V-Version mit 16-MHz-Quarz (gibt's nicht!)
			 ╔═════╦═════╦═════╗
			─╢TxD  ║ USB ║	RAW╟─
RPi (Ersatz)		─╢RxD  ║     ║	GND╟─	  ● = LED
			─╢GND  ╚═════╝	RST╟─
3P3 ───┐ ┌───	GND	─╢GND  ●	Ucc╟─
SDA ───┼─┼───	D1/SDA	─╢2   Pwr	 A3╟─
SCL ───┼─┼───	D0/SCL	─╢3		 A2╟─
P4     └─┼───	D4/3P3	─╢4		 A1╟─
GND ─────┘		─╢5		 A0╟─
			─╢6		 15╟─
			─╢7   Rx     Tx	 14╟─
			─╢8    ●     ●	 16╟─
			─╢9		 10╟─
			 ╚═════════════════╝
µC Pin	Port	Funkt.	Signal	Anwendung
  8	PB0	SS	--	rote LED „RX“, lo-aktiv
  9	PB1	SCL	-
 10	PB2	MOSI	-
 11	PB3	MISO	-
 28	PB4		-
 29	PB5	OC1A	-
 30	PB6	OC1B	-
 12	PB7	OC1C	--

 31	PC6	OC3A	-
 32	PC7	(ICP3)	--

 18	PD0	SCL	SCL	I²C-Anschluss (externer Pullup erforderlich!)
 19	PD1	SDA	SDA	I²C-Anschluss (externer Pullup erforderlich!)
 20	PD2	RxD	-
 21	PD3	TxD	-
 25	PD4	ICP1	3P3	High-Ausgang zur Vereinfachung des Steckers
 22	PD5	(XCK)	--	rote LED „TX“, lo-aktiv
 26	PD6	ADC9	--
 27	PD7	ADC10	-

 33	PE2	!HWB	--
  1	PE6	AIN0+	-

 41	PF0	ADC0	--
 40	PF1	ADC1	--
 39	PF4	ADC4	-
 38	PF5	ADC5	-
 37	PF6	ADC6	-
 36	PF7	ADC7	-

  3	-	D-		USB Data-
  4	-	D+		USB Data+
  7	-	Ubus		USB-Busspannung
 13	-	!Reset		Reset-Eingang: Taste
 16	-	XTAL2		16-MHz-Quarz
 17	-	XTAL1		16-MHz-Quarz
 42	-	AREF		Referenzspannung 2,0 V
  6	-	UCAP		Kondensator
2,34,14,44,24	UUcc,Ucc,AUcc	5P
5,15,23,35,43	UGND,GND	GND

*/

#include <avr/io.h>
#include <avr/eeprom.h>
#include <avr/pgmspace.h>	// PSTR
//#include <avr/interrupt.h>
#include <avr/signature.h>
#include <util/delay.h>
#include "usb.h"
#include <string.h>

/**********************
 * Urlader anspringen *
 **********************/
static word bootmagic NOINIT;
// Kode wird vor RAM-Initialisierung eingefügt
extern "C" void init3() __attribute__((used,naked,section(".init3")));
void init3() {
 if (bootmagic==4711) {
  bootmagic=0;		// nur einmal
  asm("jmp 0x7E00");	// Adresse des Urladers (hier: ubaboot) anspringen
 }
 MCUCR = 0x80;	// JTAG deaktivieren (sonst gehen PF4..PF7 = ADC4..ADC7 nicht)
 CLKPR = 0x80;
 CLKPR = 0;	// Volle Taktfrequenz 16 MHz
 MCUSR = 0;
 WDTCSR= 0x18;	// Watchdog töten
 WDTCSR= 0;
}

[[noreturn]] void bootstart() __attribute__((naked,noreturn));
void bootstart() {
 USBCON=0x20;	// USB terminieren: Pullup-Widerstand entfernen, das lässt sofort die Verbindung kappen
 UDCON =0x01;
// cli();
 MCUSR =0x02;	// Reset vom Eingang vorgaukeln
 bootmagic=4711;
 WDTCSR=0x18;
 WDTCSR=0x0F;	// 2 s aktivieren
 for(;;);	// Sauberen Watchdog-Reset auslösen
}
// Ich nahm an, dass der Controller stets bei Reset nach 0x7E00 springt
// und der Urlader MCUSR und PE2/!HWB einliest, aber das ist *nicht* der Fall!
// Die *Hardware* liest PE2/!HWB ein und springt bei !Reset && !!HWB
// nach 0x7E00, sonst nach 0x0000. IMHO irrsinnig, PICs lösen das in Software.
// Um dem Urlader ein lupenreines Reset anzubieten, geht es deshalb
// über einen Watchdog-Reset und über ein magisches Speicherwort
// beim nächsten Hochfahren zum Urlader.

/*************************************
 * Initialisierung und Hauptprogramm *
 *************************************/
static void setupHardware() {
 MCUCR = 0x80;	// JTAG deaktivieren (sonst gehen PF4..PF7 = ADC4..ADC7 nicht)
 CLKPR = 0x80;
 CLKPR = 0;	// Takt auf volle 16 MHz
 PORTB = 0xFF;	// Alle Pullups
 DDRB  = 0x01;	// Rx-LED aus
 PORTC = 0xC0;	// Alle Pullups
 PORTD = 0xFF;	// Alle Pullups (werden an I²C D0,D1 unwirksam)
 DDRD  = 0x30;	// Tx-LED aus, 3P3-Ausgang (für externen Pullup) ein
 PORTE = 0x44;	// Alle Pullups
 PORTF = 0xF3;	// Alle Pullups
 ACSR  = 0x80;	// Kein Analogvergleicher
 TWBR  = F_CPU/400000-16>>1;	// = 12
 TWCR  = 0x04;	// I²C aktivieren
}

static void idle() {
 usb::Poll();
}

// Hier: Nur für <=64 Byte
static byte usbEp0Read() {
 while (UENUM=0, !(UEINTX&1<<2)) usb::Poll();
 return UEDATX;
}

static void usbEp0Write(byte b) {
 UENUM=0;
 UEDATX = b;
}

static void usbEp0Commit() {
 UENUM=0;
 UEINTX=0b1111'1110;
}

static bool usbOkay() {return true;}

namespace i2c{
static byte waitint(byte=0x84);	// Default: Kein Start, kein Stop, NAK
static void waitstop();
static bool isTenBit(word);
static byte send(word,word,word);
static byte recv(word,word,word);
}

byte i2c::lastError;

static byte i2c::waitint(byte twcr) {
 TWCR = twcr;
 while (!(TWCR&0x80)) usb::Poll();
 return TWSR;
}

static void i2c::waitstop() {
 TWCR=0x94;		// Interrupt löschen, STOP auslösen
 while (TWCR&0x10) usb::Poll();	// warten bis STOP zu Ende
}

static bool i2c::isTenBit(word wValue) {
 return (wValue&0x3FF)>=0x80 || wValue>>8&f10bit;
}

static byte i2c::send(word wValue, word wIndex, word wLength) {
 byte f = wValue>>8;
 byte e;
 if (!(f&fNoStart)) {	// Startsequenz und Adresse unterdrücken?
  byte r = waitint(0xA4);		// START senden
  e=eNoStart;
  if (r!=0x08 && r!=0x10) goto stop;
  e=eNoAddr;
  if (isTenBit(wValue)) {	// 10-Bit-Adresse (W)
   TWDR = 0xF0|(wValue>>8&3)<<1;// 1111 0aa0
   if (waitint()!=0x18) goto stop;
   TWDR = wValue;
   if (waitint()!=0x28) goto stop;
  }else{			// 7-Bit-Adresse (W)
   TWDR = wValue<<1;
   if (waitint()!=0x18) goto stop;	// ACK ist Pflicht
  }
 }
 if (f&fAddr) {	// Adressbytes aus wIndex senden?
  e=eNoSub;
  TWDR = f&fAddr8 ? wIndex : wIndex>>8;
  if (waitint()!=0x28) goto stop;
  if (f&fAddrHL) {
   TWDR = f&fAddr8 ? wIndex>>8 : wIndex;
   if (waitint()!=0x28) goto stop;
  }
 }
 if (wLength) for(;;) {
  e=eUsbHickup;
  TWDR = usbEp0Read();
  if (!usbOkay()) goto stop;
  e=eEarlyNak;
  byte r = waitint();
  if (--wLength) {
   if (r==0x28) continue;	// ACK ist Pflicht
  }else{
   if (r==0x28 || !(f&fNoStop) && r==0x30) break;
  }
  goto stop;			// ACK ist Pflicht bei NoStop
 }
 e=0;
 if (!(f&fNoStop)) stop: waitstop();
 return e;
}

bool i2c::send() {
 PORTD&=~(1<<5);// Tx-LED ein
 bool ret=!(lastError=send(usb::setup.wValue,usb::setup.wIndex,usb::setup.wLength));
 PORTD|=1<<5;	// Tx-LED aus
 return ret;
}

static byte i2c::recv(word wValue,word wIndex,word wLength) {
 byte f = wValue>>8;
 byte e;
 if (!(f&fNoStart)) {
  if (f&fAddr) {		// Adressangabe?
   if (f&fNoRep) wValue&=~(fNoStop<<8);
   else wValue|=fNoStop<<8;	// 1-2 Bytes schreiben = Adresse setzen, typischerweise ohne STOP
   if (e=send(wValue,wIndex,0)) return e;
  }
  e=eNoStart+eRead;
  byte r = waitint(0xA4);		// START senden
  if (r!=0x08 && r!=0x10) goto stop;
  e=eNoAddr+eRead;
  TWDR = isTenBit(wValue)	// 10-Bit-Adresse (R)
	? 0xF0|(wValue>>8&3)<<1|1	// 1111 0aa1
	: wValue<<1|1;		// 7-Bit-Adresse (R)
  if (waitint()!=0x40) goto stop;	// ACK ist Pflicht
 }
 if (wLength) do{
  e=eEarlyNak+eRead;
  byte r = waitint(--wLength || f&fNoStop ? 0xC4 : 0x84);	// NAK beim letzten Byte
  if (r!=0x50 && r!=0x58) goto stop;
  e=eUsbHickup+eRead;
  usbEp0Write(TWDR);
  if (!usbOkay()) goto stop;
 }while (wLength);
 usbEp0Commit();
 e=0;
 if (!(f&fNoStop)) stop: waitstop();
 return e;
}

bool i2c::recv() {
 PORTB&=~(1<<0);// Rx-LED ein
 bool ret=!(lastError=recv(usb::setup.wValue,usb::setup.wIndex,usb::setup.wLength));
 PORTB|=1<<0;	// Rx-LED aus
 return ret;
}

int main() {
 setupHardware();
 usb::Init();
// sei();
 for(;;) idle();
}
Detected encoding: UTF-80