From 8800a3252dc20894088b8f6deefd3440eee4e273 Mon Sep 17 00:00:00 2001 From: Spence Konde Date: Wed, 16 Aug 2023 16:47:22 -0400 Subject: [PATCH] Fix docs --- avr/extras/ATtiny_43.md | 2 +- avr/extras/ATtiny_x41.md | 2 +- avr/extras/ATtiny_x7.md | 8 +- avr/extras/Ref_Internals.md | 2 +- avr/extras/development/removed_from_wiring.c | 360 ------------------- avr/libraries/Wire/Readme.md | 12 +- 6 files changed, 13 insertions(+), 373 deletions(-) delete mode 100644 avr/extras/development/removed_from_wiring.c diff --git a/avr/extras/ATtiny_43.md b/avr/extras/ATtiny_43.md index 07dca373..35ef68d6 100644 --- a/avr/extras/ATtiny_43.md +++ b/avr/extras/ATtiny_43.md @@ -40,7 +40,7 @@ Obviously, you can buy my lovely tiny43 board with the buck converter ready to g You want, ideally, a device which is prohibited by the laws of physics - a diode with near zero forward drop, but even more importantly, you need a diode with very VERY low reverse leakage current. If you just optiomi -### PWM frequency: +### PWM frequency TC0 is always run in Fast PWM mode: We use TC0 for millis, and phase correct mode can't be used on the millis timer - you need to read the count to get micros, but that doesn't tell you the time in phase correct mode because you don't know if it's upcounting or downcounting in phase correct mode. On this part, the TC1 is uniquely bad - it has a different, shorter list of possible WGMs, and is only 8 bits. | F_CPU | F_PWMTC0 | F_PWMTC1 | Notes | diff --git a/avr/extras/ATtiny_x41.md b/avr/extras/ATtiny_x41.md index 7d1b05a2..b3d104ec 100644 --- a/avr/extras/ATtiny_x41.md +++ b/avr/extras/ATtiny_x41.md @@ -66,7 +66,7 @@ Example of a "guard" against wrong pin mapping: All pin mapping options assume that PB2 has the LED (bootloaders will blink that pin, and LED_BUILTIN is defined as PIN_PB2), unless it's a micronucleus configuration with D+ on PB2, in which case it will instead use PB0. -### PWM frequency: +### PWM frequency TC0 is always run in Fast PWM mode: We use TC0 for millis, and phase correct mode can't be used on the millis timer - you need to read the count to get micros, but that doesn't tell you the time in phase correct mode because you don't know if it's upcounting or downcounting in phase correct mode. Unique among the tinyAVRs, the x41 parts have a third timer. TC1 and TC2 are both the "good" timers, the 16-bit-capable ones. | F_CPU | F_PWMTC0 | F_PWMTC1/2 | Notes | diff --git a/avr/extras/ATtiny_x7.md b/avr/extras/ATtiny_x7.md index caa40950..3f4b031b 100644 --- a/avr/extras/ATtiny_x7.md +++ b/avr/extras/ATtiny_x7.md @@ -175,16 +175,16 @@ Mironucleus used: Micronucleus boards are locked to the crystal, no oscillator c ### Purchasing ATtiny167 Boards I (Spence Konde / Dr. Azzy) sell ATtiny167 boards through my Tindie store - your purchases support the continued development of this core. A new version is now available. In order to fit in the same form factor as my other tinyAVR breakout boards, these use the TSSOP-20 package version insteaad of the bulky SOIC-20. -* [Azduino Tiny167 Pro - bare board]() -* [Azduino Tiny167 Pro]() -* [Ultramini - fits a DIP-24 socket]() +* Azduino Tiny167 Pro - bare board - pending verification of functionality. +* Azduino Tiny167 Pro - pending verification and assembly. +* Ultramini - fits a DIP-24 socket - pending verification of functionality. ## Package variants The 87 and 167 are available in three package variations. Additionally, the 167 only can be had in a fourth package. * SOIC-20 (wide) - bigger than the side of a house, but easy to hand solder * TSSOP-20 - Slightly more demanding to solder. While it is hard to imagine being able to read this text and miss a bridge between adjacent pins on a SOIC-20, the same cannot be said for a SSOP-20 - depending on your eyesight, you may need magnification or more attention to lighting in order to spot bridges visually * VQFN32 - with 12 unused pins - Atmel seemed to REALLY like this package - a lot of 20-pin tinyAVR parts got this as their QFN instead of a proper QFN20-type. It's an annoying package, though - it's very fine pitch, large for a QFN (5mm x 5mm), and the unused pins don't appear to have been arranged with consideration of the layout. They're in the same order as the pins up and down the two sides of the SOIC/SSOP parts (probably a technical constraint I've never seen a chip with what was belived to have the same die, *not* have the same pin order, so I think bond wires have to make straight lines that don't cross each other from the die to the pin), but the decisons for where those dummy pins would go appears to have been based only on their convenience. -* WQFN20 (167 only) - the only time, to my knowledge, that a new package option has been added for a classic AVR after the Microchip buyout. That this has only happened once, despite many examples of disappointing packages fromthe past, so I have to imagine that one or more very large customers (they're automotive parts, so the main buyers tend to be small in number but make up for it in volume) was giving them holy hell over that 32-pin package. The 87 did not get the same blessing (nor did the 861, which was also a 20-pin tiny stuck in a 32-pin VQFN), nor did any other part they have made. They have also not added any packages to a post-revolutionary part either, even when we can show that the die size would would work fine based on what they've already fit it into, and it is plain to see the that the product is held back by it's current package options. +* WQFN20 (167 only) - the only time, to my knowledge, that a new package option has been added for a classic AVR after the Microchip buyout. That this has only happened once, despite many examples of disappointing packages fromthe past, leads me to belive that one or more very large customers (they're automotive parts, so the main buyers tend to be small in number but make up for it in volume) was giving them holy hell over that 32-pin package. The 87 did not get the same blessing (nor did the 861, which was also a 20-pin tiny stuck in a 32-pin VQFN), nor did any other part they have made. They have also not added any packages to a post-revolutionary part either, even when we can show that the die size would would work fine based on what they've already fit it into, and it is plain to see the that the product is held back by it's current package options. ## Interrupt Vectors This table lists all of the interrupt vectors available on the ATtiny x7-family, aas well as the name you refer to them as when using the `ISR()` macro. Be aware that a non-existent vector is just a "warning" not an "error" (for example, if you misspell a vector name) - however, when that interrupt is triggered, the device will (at best) immediately reset (and not clearly - I refer to this as a "dirty reset") The catastrophic nature of the failure often makes debugging challenging. diff --git a/avr/extras/Ref_Internals.md b/avr/extras/Ref_Internals.md index 33ad39bc..1d40ebfb 100644 --- a/avr/extras/Ref_Internals.md +++ b/avr/extras/Ref_Internals.md @@ -1,4 +1,4 @@ -# Internals and advanced functionality: +# Internals and advanced functionality You can identify the core using the following: diff --git a/avr/extras/development/removed_from_wiring.c b/avr/extras/development/removed_from_wiring.c deleted file mode 100644 index 7685b048..00000000 --- a/avr/extras/development/removed_from_wiring.c +++ /dev/null @@ -1,360 +0,0 @@ - -/* Delay for the given number of microseconds. Assumes a 1, 8, 12, 16, 20 or 24 MHz clock. */ -//Not used anymore, we have the version we stole from nerdralph's picocore! - -/* -void delayMicroseconds(uint16_t us) -{ - #define _MORENOP_ "" // redefine to include NOPs depending on frequency - - // call = 4 cycles + 2 to 4 cycles to init us(2 for constant delay, 4 for variable) - - // calling avrlib's delay_us() function with low values (e.g. 1 or - // 2 microseconds) gives delays longer than desired. - //delay_us(us); - #if F_CPU >= 24000000L - // for the 24 MHz clock for the adventurous ones, trying to overclock - - // zero delay fix - if (!us) return; // = 3 cycles, (4 when true) - - // the following loop takes a 1/6 of a microsecond (4 cycles) - // per iteration, so execute it six times for each microsecond of - // delay requested. - us *= 6; // x6 us, = 7 cycles - - // account for the time taken in the preceding commands. - // we just burned 22 (24) cycles above, remove 5, (5*4=20) - // us is at least 6 so we can subtract 5 - us -= 5; //=2 cycles - - #elif F_CPU >= 20000000L - // for the 20 MHz clock on rare Arduino boards - - // for a one-microsecond delay, simply return. the overhead - // of the function call takes 18 (20) cycles, which is 1us - __asm__ __volatile__ ( - "nop" "\n\t" - "nop" "\n\t" - "nop" "\n\t" - "nop"); //just waiting 4 cycles - if (us <= 1) return; // = 3 cycles, (4 when true) - - // the following loop takes a 1/5 of a microsecond (4 cycles) - // per iteration, so execute it five times for each microsecond of - // delay requested. - us = (us << 2) + us; // x5 us, = 7 cycles - - // account for the time taken in the preceding commands. - // we just burned 26 (28) cycles above, remove 7, (7*4=28) - // us is at least 10 so we can subtract 7 - us -= 7; // 2 cycles - - #elif F_CPU >= 18432000L - // for a one-microsecond delay, simply return. the overhead - // of the function call takes 18 (20) cycles, which is approx. 1us - __asm__ __volatile__ ( - "nop" "\n\t" - "nop" "\n\t" - "nop" "\n\t" - "nop"); //just waiting 4 cycles - - if (us <= 1) return; // = 3 cycles, (4 when true) - - // the following loop takes nearly 1/5 (0.217%) of a microsecond (4 cycles) - // per iteration, so execute it five times for each microsecond of - // delay requested. - us = (us << 2) + us; // x5 us, = 7 cycles - - // user wants to wait 7us or more -- here we can use approximation - if (us > 34) { // 3 cycles - // Since the loop is not accurately 1/5 of a microsecond we need - // to multiply us by (18.432 / 20), very close to 60398 / 2.**16. - - // Approximate (60398UL * us) >> 16 by using 60384 instead. - // This leaves a relative error of 232ppm, or 1 in 4321. - unsigned int r = us - (us >> 5); // 30 cycles - us = r + (r >> 6) - (us >> 4); // 55 cycles - // careful: us is generally less than before, so don't underrun below - - // account for the time taken in the preceding and following commands. - // we are burning 114 (116) cycles, remove 29 iterations: 29*4=116. - - TODO: is this calculation correct. Right now, we do - function call 6 (+ 2) cycles - wait at top 4 - comparison false 3 - multiply by 5 7 - comparison false 3 - compute r 30 - update us 55 - subtraction 2 - return 4 - total --> 114 (116) cycles - - - // us dropped to no less than 32, so we can subtract 29 - us -= 29; // 2 cycles - } else { - // account for the time taken in the preceding commands. - // we just burned 30 (32) cycles above, remove 8, (8*4=32) - // us is at least 10, so we can subtract 8 - us -= 8; // 2 cycles - } - - #elif F_CPU >= 18000000L - // for the 18 MHz clock, if somebody is working with USB - // or otherwise relating to 12 or 24 MHz clocks - - // for a 1 microsecond delay, simply return. the overhead - // of the function call takes 14 (16) cycles, which is .8 us - if (us <= 1) return; // = 3 cycles, (4 when true) - - // make the loop below last 6 cycles - #undef _MORENOP_ - #define _MORENOP_ " nop \n\t nop \n\t" - - // the following loop takes 1/3 of a microsecond (6 cycles) per iteration, - // so execute it three times for each microsecond of delay requested. - us = (us << 1) + us; // x3 us, = 5 cycles - - // account for the time taken in the preceding commands. - // we burned 20 (22) cycles above, plus 2 more below, remove 4 (4*6=24), - // us is at least 6 so we may subtract 4 - us -= 4; // = 2 cycles - - #elif F_CPU >= 16500000L - // for the special 16.5 MHz clock - - // for a one-microsecond delay, simply return. the overhead - // of the function call takes 14 (16) cycles, which is about 1us - if (us <= 1) return; // = 3 cycles, (4 when true) - - // the following loop takes 1/4 of a microsecond (4 cycles) times 32./33. - // per iteration, thus rescale us by 4. * 33. / 32. = 4.125 to compensate - us = (us << 2) + (us >> 3); // x4.125 with 23 cycles - - // account for the time taken in the preceding commands. - // we burned 38 (40) cycles above, plus 2 below, remove 10 (4*10=40) - // us is at least 8, so we subtract only 7 to keep it positive - // the error is below one microsecond and not worth extra code - us -= 7; // = 2 cycles - - #elif F_CPU >= 16000000L - // for the 16 MHz clock on most Arduino boards - - // for a one-microsecond delay, simply return. the overhead - // of the function call takes 14 (16) cycles, which is 1us - if (us <= 1) return; // = 3 cycles, (4 when true) - - // the following loop takes 1/4 of a microsecond (4 cycles) - // per iteration, so execute it four times for each microsecond of - // delay requested. - us <<= 2; // x4 us, = 4 cycles - - // account for the time taken in the preceding commands. - // we just burned 19 (21) cycles above, remove 5, (5*4=20) - // us is at least 8 so we can subtract 5 - us -= 5; // = 2 cycles, - - #elif F_CPU >= 14745600L - // for a one-microsecond delay, simply return. the overhead - // of the function call takes 14 (16) cycles, which is approx. 1us - - if (us <= 1) return; // = 3 cycles, (4 when true) - - // the following loop takes nearly 1/4 (0.271%) of a microsecond (4 cycles) - // per iteration, so execute it four times for each microsecond of - // delay requested. - us <<= 2; // x4 us, = 4 cycles - - // user wants to wait 8us or more -- here we can use approximation - if (us > 31) { // 3 cycles - // Since the loop is not accurately 1/4 of a microsecond we need - // to multiply us by (14.7456 / 16), very close to 60398 / 2.**16. - - // Approximate (60398UL * us) >> 16 by using 60384 instead. - // This leaves a relative error of 232ppm, or 1 in 4321. - unsigned int r = us - (us >> 5); // 30 cycles - us = r + (r >> 6) - (us >> 4); // 55 cycles - // careful: us is generally less than before, so don't underrun below - - // account for the time taken in the preceding and following commands. - // we are burning 107 (109) cycles, remove 27 iterations: 27*4=108. - - // us dropped to no less than 29, so we can subtract 27 - us -= 27; // 2 cycles - } else { - // account for the time taken in the preceding commands. - // we just burned 23 (25) cycles above, remove 6, (6*4=24) - // us is at least 8, so we can subtract 6 - us -= 6; // 2 cycles - } - - #elif F_CPU >= 12000000L - // for the 12 MHz clock if somebody is working with USB - - // for a 1 microsecond delay, simply return. the overhead - // of the function call takes 14 (16) cycles, which is 1.3us - if (us <= 1) return; // = 3 cycles, (4 when true) - - // the following loop takes 1/3 of a microsecond (4 cycles) - // per iteration, so execute it three times for each microsecond of - // delay requested. - us = (us << 1) + us; // x3 us, = 5 cycles - - // account for the time taken in the preceding commands. - // we just burned 20 (22) cycles above, remove 5, (5*4=20) - // us is at least 6 so we can subtract 5 - us -= 5; //2 cycles - - #elif F_CPU >= 8000000L - // for the 8 MHz internal clock - - // for a 1 and 2 microsecond delay, simply return. the overhead - // of the function call takes 14 (16) cycles, which is 2us - if (us <= 2) return; // = 3 cycles, (4 when true) - - // the following loop takes 1/2 of a microsecond (4 cycles) - // per iteration, so execute it twice for each microsecond of - // delay requested. - us <<= 1; //x2 us, = 2 cycles - - // account for the time taken in the preceding commands. - // we just burned 17 (19) cycles above, remove 4, (4*4=16) - // us is at least 6 so we can subtract 4 - us -= 4; // = 2 cycles - - #elif F_CPU >= 6000000L - // for that unusual 6mhz clock... - - // for a 1 to 3 microsecond delay, simply return. the overhead - // of the function call takes 14 (16) cycles, which is 2.5us - if (us <= 3) return; // = 3 cycles, (4 when true) - - // make the loop below last 6 cycles - #undef _MORENOP_ - #define _MORENOP_ " nop \n\t nop \n\t" - - // the following loop takes 1 microsecond (6 cycles) per iteration - // we burned 15 (17) cycles above, plus 2 below, remove 3 (3 * 6 = 18) - // us is at least 4 so we can subtract 3 - us -= 3; // = 2 cycles - - #elif F_CPU >= 4000000L - // for that unusual 4mhz clock... - - // for a 1 to 4 microsecond delay, simply return. the overhead - // of the function call takes 14 (16) cycles, which is 4us - if (us <= 4) return; // = 3 cycles, (4 when true) - - // the following loop takes 1 microsecond (4 cycles) - // per iteration, so nothing to do here! \o/ - // ... in terms of rescaling. We burned 15 (17) above plus 2 below, - // so remove 5 (5 * 4 = 20), but we may at most remove 4 to keep us > 0. - us -= 4; // = 2 cycles - - #elif F_CPU >= 2000000L - // for that unusual 2mhz clock... - - // for a 1 to 9 microsecond delay, simply return. the overhead - // of the function call takes 14 (16) cycles, which is 8us - if (us <= 9) return; // = 3 cycles, (4 when true) - // must be at least 10 if we want to do /= 2 -= 4 - - // divide by 2 to account for 2us runtime per loop iteration - us >>= 1; // = 2 cycles; - - // the following loop takes 2 microseconds (4 cycles) per iteration - // we burned 17 (19) above plus 2 below, - // so remove 5 (5 * 4 = 20), but we may at most remove 4 to keep us > 0. - us -= 4; // = 2 cycles - - #else - // for the 1 MHz internal clock (default settings for common AVR microcontrollers) - // the overhead of the function calls is 14 (16) cycles - if (us <= 16) return; //= 3 cycles, (4 when true) - if (us <= 25) return; //= 3 cycles, (4 when true), (must be at least 25 if we want to subtract 22) - - // compensate for the time taken by the preceding and next commands (about 22 cycles) - us -= 22; // = 2 cycles - // the following loop takes 4 microseconds (4 cycles) - // per iteration, so execute it us/4 times - // us is at least 4, divided by 4 gives us 1 (no zero delay bug) - us >>= 2; // us div 4, = 4 cycles - #endif - - // busy wait - __asm__ __volatile__ ( - "1: sbiw %0,1" "\n\t" // 2 cycles - _MORENOP_ // more cycles according to definition - "brne 1b" : "=w" (us) : "0" (us) // 2 cycles - ); - // return = 4 cycles -} -*/ - - -/*#if (defined(__AVR_ATtinyX41__) && F_CPU == 16000000 && CLOCK_SOURCE == 0 ) - // functions related to the 16 MHz internal option on ATtiny841/441. - // 174 CALBOOST seems to work very well - it gets almost all of them close enough for USART, which is what matters. It was empirically determined from a few parts I had lying around. - #define TINYX41_CALBOOST 174 - static uint8_t tinyx41_cal16m = 0; - static uint8_t saveTCNT = 0; - void oscBoost() { - OSCCAL0 = (origOSC>MAXINITCAL?255:(origOSC + CALBOOST)); - _NOP(); - } - void oscSafeNVM() { // called immediately prior to writing to EEPROM. - //TIMSK0& = ~(_BV(TOIE0)); // turn off millis interrupt - let PWM keep running (Though at half frequency, of course!) - //saveTCNT = TCNT0; - //if (TIFR0&_BV(TOV0)) { // might have just missed interrupt - recording as 255 is good enough (this may or may not have been what we recorded, but if it was still set, interrupt didn't fire) - // saveTCNT = 255; - //} - #ifndef DISABLEMILLIS - saveMillis = millis(); //save low bytes of millis - #endif - set_OSCCAL(read_factory_calibration()); - } -*/ - //void oscDoneNVM(uint8_t bytes_written) { - /* That's the number of bytes of eeprom written, at 3.3ms or so each. - * EEPROM does it one at a time, but user code could call these two methods when doing block writes (up to 64 bytes). Just be sure to do the eeprom_busy_wait(); at the end, as in EEPROM.h. - * Not so much because it's a prerequisite for this stupid correction to timing but because cranking the oscillator back up during the write kinda defeats the point of slowing it doewn... - * 3.3ms is good approximation of the duration of writing a byte - it'll be about 3~4% faaster since we're running around 5V at default call - hence, we're picking 3.3ms - the oscillator - * adjustment loops and these calculations should be fast enough that the time they dont take long enough to worry about... - * relies on assumptions from implementation above of millis on this part at 16MHz! - * millis interrupt was disabled when oscSaveNVM() was called - so we don't need to do anything fancy to access the volatile variables related to it. - * 1 millis interrupt fires every 1.024ms, so we want 3.3/1.024= 3.223 overflows; there are 256 timer ticksin an overflow, so 57 timer ticks... - */ - // FRACT_MAX = 125, FRACT_INC =3 - //set_OSCCAL(tinyx41_cal16m); //stored when we initially tuned - //#ifndef DISABLEMILLIS - /* - uint8_t m = 3 * bytes_written; // the 3 whole overflows - uint16_t tickcount = 57*bytes_written + saveTCNT; - m += (tickcount >> 8); // overflows from theose extra /0.223's - millis_timer_overflow_count += m; // done with actual overflows, so record that. - uint16_t f = FRACT_INC*m + millis_timer_fract; // (m could be up to 207) - while(f > FRACT_MAX){ // at most 621 + 124 = 745 - f -= FRACT_MAX; - m++; - } - // now we're adding up the contributions to millis from the 0.024 part... - // save the results - millis_timer_fract = f; - millis_timer_millis += m; - TCNT0 = 0; - TIFR0 |= _BV(TOV0); // clear overflow flag - TIMSK0 |= _BV(TOIE0); // enable overflow interrupt - TCNT0 = tickcount; // restore new tick count - // wonder if it was worth all that just to write to the EEPROM while running at 16MHz off internal oscillator without screwing up millis and micros... - */ - // I don't think it was, gonna go with a quicker dirtier method - we instead leave it running at half speed, saving the low byte of millis. Longest time at half-speed - // is 3.3 * 64 around 200 ms for a max length block write. So if we leave millis running, and know that it's running at half speed... just take difference of the LSB - // and add that much to millis. - //(uint8_t)millis_timer_overflow_count - //uint8_t milliserror=((uint8_t) millis())-saveMillis - //#endif - //} -//#endif diff --git a/avr/libraries/Wire/Readme.md b/avr/libraries/Wire/Readme.md index 239d52b8..711cabe7 100644 --- a/avr/libraries/Wire/Readme.md +++ b/avr/libraries/Wire/Readme.md @@ -73,14 +73,14 @@ On these devices, when the master writes: 2. Master clocks out the 7 bit part address and the Write bit 3. Slave ACKs 4. Master clocks out the address. Slave sets the pointer to this and acks (rarely, the second byte is also part of the pointer address (usually encountered with EEPROMs)) -5. Master clocks out one byte of data to write tothat address (most devices autoincrement, a few dont) -6. Slave writes it to that location in the virtual register table and acks +5. Master clocks out one byte of data to write tothat address (most devices autoincrement, a few don't, some have an option for that in some register). +6. Slave writes it to to it's internal SFRs or RAM as appropriate. 7. Repeat 5 and 6 until all data transferred. -8. Master generates a stop condition +8. Master responds with a NACK and generates a stop condition. For a read, the master would first perform the first 4 steps above setting the location they want to read from, then either send a stop, then start condition or a repeated start and then reads per steps 5 to 8 of the way the Arduino API provides. -### Yeah, they don't line up so good. -It's like the API designer read the protocol spec and designed to that and had never actually used an I2C device. You cannot have a register-model, because you don't know how many bytes the master will read (the protocol never tells you this), nor can you find out how many are read after the fact, nor can you put the slave to sleep because it might be silently servicing an interrupt for a read that hasn't finished yet. This will generally make you "that device" that when misused, becomes non-responsive with one or both being held low. You don't want to be that device. +### Yeah, they don't line up so good +It's like the API designer read the protocol spec and designed to that and had never actually used an I2C device, or had little imagination and hadn't attempted to make anything that acted like other I2C devices. You cannot have a register-model, because you don't know how many bytes the master will read (the protocol never tells you this), nor can you find out how many are read after the fact, nor can you put the slave to sleep because it might be silently servicing an interrupt for a read that hasn't finished yet. This will generally make you "that device" that when misused, becomes non-responsive with one or both lines being held low. You don't want to be that device. I don't really have any good solution to offer here, but this is why all arduino slave devices you've seen use I2C like lobotomized serial: That's the only mode of operation that the API supports. -The API has been extended for DxCore and mTC, as the problem was far more tractable there (not only is it a single implementation covering a much more cooperative peripheral, the same library works unmodified and likely will continue to do so for the forseeable future. +The API has been extended for DxCore and mTC - the problem was far more tractable there: It only had to be done once for every AVR released since 2016 - the same library works unmodified and likely will continue to do so for the forseeable future, with only trivial changes, whereas it would have to be done thrice for the older parts. Moreover, on all of these parts, whatever I2C implementation is available, it is much less helpful, and lacks the features that we used for this on mTC and DxC.