diff --git a/megaavr/boards.txt b/megaavr/boards.txt index a8866ec7..464a37cc 100644 --- a/megaavr/boards.txt +++ b/megaavr/boards.txt @@ -216,7 +216,6 @@ atxy7.menu.chip.817.build.board=AVR_ATtiny817 atxy7.menu.chip.817.build.attiny=817 atxy7.menu.chip.817.upload.maximum_size=8192 atxy7.menu.chip.817.build.mrelax= -atxy7.menu.chip.817.build.mrelax= atxy7.menu.chip.817.upload.maximum_data_size=512 atxy7.menu.chip.817.bootloader.TCD0CFG_avrdude="-Ufuse4:w:0x00:m" atxy7.menu.chip.817.bootloader.TCD0CFG_serialupdi=4:0x00 diff --git a/megaavr/cores/megatinycore/Arduino.h b/megaavr/cores/megatinycore/Arduino.h index 89641c07..375fa43c 100644 --- a/megaavr/cores/megatinycore/Arduino.h +++ b/megaavr/cores/megatinycore/Arduino.h @@ -6,17 +6,17 @@ * ATtiny 0/1/2-series microcontrollers from Microchip. * megaTinyCore is free software (LGPL 2.1) * See LICENSE.txt for full legal boilerplate if you must */ + /************************************************************* * This file contains the stuff I think people are most likely * to need to refer to. The minutia has all been pushed into * core_devices.h if it's independent of pins_arduino.h or into * pinswap.h if it relates to PORTMUX, which is a great volume - * of stuff nobody should have to read.= - */ - /* + * of stuff nobody should have to read. + * * That means functions and macros that may be used by user code - * (except for part-feature ones - those are clearly documented - * in the readme if they are ready for users). + * (except for some part info macros, which are described in + * Ref_Defines.md in the documentation. * I also try to put detailed comments in where appropriate. *************************************************************/ @@ -45,9 +45,15 @@ * analogWrite() on a constant pin that will never support PWM, or digitalWrite() on * a pin number that is neither a pin nor NOT_A_PIN (which silently does nothing for * compatibility). + * * badCall() on the other hand is called if we know that regardless of what arguments * are passed, that function is nonsensical with current settings, for example, millis() - * when millis timekeeping has been disabled */ + * when millis timekeeping has been disabled. + * Closely related in check_constant_pin, which calls badArg to stop compilation if + * the pin number it was passed isn't a foldably constant value known at compile time, + * which is used by the fast digital I/O functions and other things that require than + * you pass them a *constant* pin. + */ #if !defined(LTODISABLED) void badArg(const char*) __attribute__((error(""))); diff --git a/megaavr/libraries/Comparator/README.md b/megaavr/libraries/Comparator/README.md index 0193e020..abdb5d0e 100644 --- a/megaavr/libraries/Comparator/README.md +++ b/megaavr/libraries/Comparator/README.md @@ -1,6 +1,6 @@ # Comparator A library for interfacing with the analog comparator peripheral in the modern AVR parts - tinyAVR 0/1/2-series, megaAVR 0-series, and the latest iterations, the DA, DB, DD, and EA-series. -Developed by [MCUdude](https://github.com/MCUdude/) with some porting effort by [Spence Konde](https://github.com/SpenceKonde/). This is the readme distributed with megaTinyCore. +Developed by [MCUdude](https://github.com/MCUdude/) with some porting effort by [Spence Konde](https://github.com/SpenceKonde/). This is the readme distributed with DxCore. The tinyAVR 0-series, 2-series, and 1-series parts with less than 16k of flash have a single analog comparator, with either 1 or 2 options each for the positive and negative input pins. The "golden" 1-series parts, those with 16k or 32k flash (and other goodies) instead have three comparators, which have up to 4 options for the positive input, and 2 for the negative. The internal voltage reference can also be used as the negative side; on the 1-series and 2-series parts, that reference can be scaled by an internal 8-bit DAC, and on 0-series and 1-series parts, the reference itself can be used directly. This library provides a wrapper class, `Comparator` that exposes the full functionality of these peripherals without having to manually manipulate registers. @@ -41,14 +41,14 @@ Like the other basic wrappers around modern avr peripherals (logic, ZCD, event), ### The POWER and LPMODE options are not supported currently On Dx-series parts, each analog comparator can be configured for one of three power profiles. The higher the number, the lower the power consumption and the slower the response. Currently the Comparator library always uses the highest power, fastest response option. This may be changed in a future release if there is call for such a feature. -Early versions of the header specified a fourth value, but this was removed from both the datasheet and the io header before release. It may or may not secretly still be present (my guess would be that it is, just like the 4x PLL multiplier), but there was likely a reason that it was struck from the documentation (likely, that it didn't work, was inaccurate, or didn't save power). +Early versions of the header specified a fourth value, but this was removed from both the datasheet and the io header before release. It may or may not secretly still be present (my guess would be that it is, just like the 4x PLL multiplier), but there was likely a reason that it was struck from the documentation (likely, that it didn't work, was inaccurate, or didn't save power). It would appear that by the time the DD was released, they'd gotten the issue sorted out, because power profile 3 is present there. On the tinyAVR and megaAVR parts, there is instead an LPMODE (Low Power Mode) which can be either on or off. Comparator always sets it to 0. An option to configure this may be made available in the future if there is user demand. ### Window Mode is not supported Thus far all modern AVR parts with more than 1 comparator have had a "windowed mode" that can be selected to group 2 comparators into a single "windowed" comparator, where both comparators must use the same positive input, while the negative inputs define the upper and lower bounds of the "window", and interrupts can be generated when the state rises above, or falls below the input, or when it enters or leaves the window. -The Comparator library does not support this odd option. There are no plans to add support for this odd feature. Note also that this is entirely separate from the ADC "window comparator" mode, where a similar effect is achieved with the ADC set in free-running mode. +The Comparator library does not support this odd option. There are no plans to add support for this feature - it's an awkward amount of synchronization for this library to provide. Note also that this is entirely separate from the ADC "window comparator" mode, where a similar effect is achieved with the ADC set in free-running mode! ## Properties of the Comparator class @@ -92,7 +92,9 @@ Comparator.input_n = comparator::in_n::vref; // Connect voltage reference to th ### reference -On the 0-series and 1-series, this sets the voltage reference that will be used if VREF is selected as the negative input. On 1-series and 2-series, this also sets the voltage that DACREF is derived from. On 1-series parts with multiple comparators, these reference voltages can be set independently (unlike on the Dx-series). On the tinyAVR 1-series, AC0 and the DAC that can be output on PA6 share the same reference. The 0/1-series uses a rather strange set of voltages, while the 2-series uses the same voltages that most modern AVRs do +On the 0-series and 1-series, this sets the voltage reference that will be used if VREF is selected as the negative input. On non-0-series, this also sets the voltage that DACREF is derived from. + +On 1-series parts with multiple comparators, these reference voltages can be set independently for each comparator. On the Dx and Ex parts, parts with multiple comparators have to share just one reference voltage.. On the tinyAVR 1-series, AC0 and the DAC that can be output on PA6 share the same reference. The 0/1-series uses a rather strange set of voltages, while the 2-series uses the same voltages that most modern AVRs do. Accepted values (0/1-series): ``` c++ @@ -105,7 +107,7 @@ comparator::ref::vref_4v34; // 4.34V internal reference comparator::ref::vref_2v500; // 2.5V internal reference (compatibility) ``` -Accepted values (2-series): +Accepted values (Everything else): ``` c++ comparator::ref::disable; // Do not use any reference comparator::ref::vref_1v024; // 1.02V internal reference diff --git a/megaavr/libraries/Comparator/examples/Internal_reference/Internal_reference.ino b/megaavr/libraries/Comparator/examples/Internal_reference/Internal_reference.ino index bcd43fd0..49b02af1 100644 --- a/megaavr/libraries/Comparator/examples/Internal_reference/Internal_reference.ino +++ b/megaavr/libraries/Comparator/examples/Internal_reference/Internal_reference.ino @@ -1,4 +1,4 @@ -/***********************************************************************| + /***********************************************************************| | Modern AVR Comparator library for tinyAVR 0/1/2, megaAVR0, Dx, and Ex| | | | Developed in 2019 by MCUdude https://github.com/MCUdude/ | @@ -33,7 +33,7 @@ void setup() { // Configure relevant comparator parameters - #if MEGATINYCORE_SERIES > 0 + #if defined(MEGATINYCORE) && MEGATINYCORE_SERIES > 0 Comparator.input_p = comparator::in_p::in0; // Use positive input 0 (PD2) Comparator.input_n = comparator::in_n::dacref; // Connect the negative pin to the DACREF voltage Comparator.reference = comparator::ref::vref_2v5; // Set the DACREF voltage to 2.5V diff --git a/megaavr/libraries/Comparator/examples/Interrupt/Interrupt.ino b/megaavr/libraries/Comparator/examples/Interrupt/Interrupt.ino index 02b677f8..6339219b 100644 --- a/megaavr/libraries/Comparator/examples/Interrupt/Interrupt.ino +++ b/megaavr/libraries/Comparator/examples/Interrupt/Interrupt.ino @@ -2,8 +2,10 @@ | Modern AVR Comparator library for tinyAVR 0/1/2, megaAVR0, Dx, and Ex| | | | Developed in 2019 by MCUdude https://github.com/MCUdude/ | -| Ported to tinyAVR & Dx-series by Spence Konde for megaTinyCore and | -| DxCore 2021-2022: https://github.com/SpenceKonde/ | +| Ported to tinyAVR 2021 by Spence Konde for megaTinyCore | +| https://github.com/SpenceKonde/megaTinyCore | +| Ported to tinyAVR 2022 by Spence Konde for DxCore | +| https://github.com/SpenceKonde/DxCore | | | | In this example we use an internal reference voltage instead of an | | external one on the negative pin. This makes it possible to give a | @@ -49,7 +51,7 @@ void setup() { // Configure relevant comparator parameters Comparator.input_p = comparator::in_p::in0; // Use positive input 0 (PA7) - #if MEGATINYCORE_SERIES == 0 + #if (defined(MEGATINYCORE) && MEGATINYCORE_SERIES == 0) Comparator.input_n = comparator::in_n::vref; // 0-series has no DACREF, so use vref directly. #else Comparator.input_n = comparator::in_n::dacref; // Connect the negative pin to the DACREF voltage diff --git a/megaavr/libraries/Comparator/examples/Simple_comparator/Simple_comparator.ino b/megaavr/libraries/Comparator/examples/Simple_comparator/Simple_comparator.ino index c3855199..f3ba6903 100644 --- a/megaavr/libraries/Comparator/examples/Simple_comparator/Simple_comparator.ino +++ b/megaavr/libraries/Comparator/examples/Simple_comparator/Simple_comparator.ino @@ -7,24 +7,29 @@ | Developed in 2019-2022 by MCUdude | | https://github.com/MCUdude/ | | | -| Ported to tinyAVR 2020-2021 by Spence Konde. | +| Ported 2021-2022 to DxCore, megaTinyCore by Spence Konde. | | | | | In this example we use the negative and positive input 0 of the | | comparator. The output goes high if the positive input is higher than | | the negative input, and low otherwise. | +| This is much like tranplanting an 'lm339" only with better specs into | +| your AVR. Pin used are: | +| | Dx/Ex/Mega0 | TinyAVR | DD14 | | +| In_p | PD2 | PA7 | AINP3 PB | +| in_N | PD3 | PA6 | | | +| Out | PA7 | PA5 | Todo: evsys| | | +| | +|Note that the evetn library can by used to redirect the outputt | | -| In effect this turns the three pins into the inputs and outputs of a | -| LM339-like chip, except that these are rail to rail |***********************************************************************/ #include void setup() { // Configure relevant comparator parameters - Comparator.input_p = comparator::in_p::in0; // Use positive input 0 (PA7) - Comparator.input_n = comparator::in_n::in0; // Use negative input 0 (PA6) - Comparator.output = comparator::out::enable; // Enable output on PIN_PA5 (digital pin 1) - // // or PIN_PA3 (digital pin 4) on ATtiny402/202) + Comparator.input_p = comparator::in_p::in0; // Use positive input 0 (PD2/PA7) + Comparator.input_n = comparator::in_n::in0; // Use negative input 0 (PD3/PA6) + Comparator.output = comparator::out::enable; // Enable output - (PA7/PA5) // Initialize comparator Comparator.init(); diff --git a/megaavr/libraries/Comparator/src/Comparator.cpp b/megaavr/libraries/Comparator/src/Comparator.cpp index bee4b907..06ec4d70 100644 --- a/megaavr/libraries/Comparator/src/Comparator.cpp +++ b/megaavr/libraries/Comparator/src/Comparator.cpp @@ -18,6 +18,14 @@ AltOUT | PIN_PC6* | PIN_PC6* | PIN_PC6* | n/a | PIN_PC6* | PIN_PC6* | */ #if (defined(ANALOG_COMP_PINS_DA_DB)) /* P0, P1, P2, P3, N0, N1, N2 */ + #if defined(MVIO) && !defined(PORTE) + /* if we have MVIO on a DB that has 32 or 28 pins, there's no PD0. + * We can test whether they have that pin by looking for PORTE, which is only present on + * parts with at least 48 pins, which will also have a PD0. Parts without MVIO always have PD0.*/ + #define PORTDPIN0 AC_NULL_REG + #else + #define PORTDPIN0 PORTD.PIN0CTRL + #endif #if defined(AC0_AC_vect) #if defined(PORTE) AnalogComparator Comparator0(0, AC0, PORTD.PIN2CTRL, PORTE.PIN0CTRL, PORTE.PIN2CTRL, PORTD.PIN6CTRL, PORTD.PIN3CTRL, PORTD.PIN0CTRL, PORTD.PIN7CTRL); @@ -32,30 +40,41 @@ AltOUT | PIN_PC6* | PIN_PC6* | PIN_PC6* | n/a | PIN_PC6* | PIN_PC6* | #if defined(PORTE) AnalogComparator Comparator2(2, AC2, PORTD.PIN2CTRL, PORTD.PIN4CTRL, PORTE.PIN1CTRL, PORTD.PIN6CTRL, PORTD.PIN7CTRL, PORTD.PIN0CTRL, PORTD.PIN7CTRL); #else - AnalogComparator Comparator2(2, AC2, PORTD.PIN2CTRL, PORTD.PIN4CTRL, AC_NULL_REG, PORTD.PIN6CTRL, PORTD.PIN7CTRL, PORTD.PIN0CTRL, PORTD.PIN7CTRL); + AnalogComparator Comparator2(2, AC2, PORTD.PIN2CTRL, PORTD.PIN4CTRL, AC_NULL_REG, PORTD.PIN6CTRL,/* AVR DA/DB-series has no in_p4 */ PORTD.PIN7CTRL, PORTD.PIN0 PORTD.PIN7CTRL); #endif #endif #elif defined(ANALOG_COMP_PINS_DD) /* DD:1 AC: P0, P3, P4, N0, N2, N3 */ + /* IS_MVIO_ENABLED() may *or may not* be a compile time known constant! + * This depends on the tools menu option selected for MVIO, and whether we can assume that the code is running on a properly configured part. + * That is a safe assumption when we build for direct upload, since users will upload the code via UPDI and we can set the fuse then. + * When using a bootloader, we have no such guarantee - we can't set the fuses when we upload, so unless the user tells us that they + * really are sure it's configured right, we will test whether MVIO is enabled. + * In any event, PD2 is only present on parts with the nearly complete PORTD. */ + #if _AVR_PINCOUNT > 20 + #define PORTDPIN2 PORTD.PIN2CTRL + #else + #define PORTDPIN2 AC_NULL_REG + #endif #if defined(AC0_AC_vect) - AnalogComparator Comparator0(0, AC0, PORTD.PIN2CTRL, PORTD.PIN6CTRL, (IS_MVIO_ENABLED() ? AC_NULL_REG : PORTC.PIN3CTRL) PORTC.PIN3CTRL, PORTD.PIN3CTRL, PORTD.PIN7CTRL, (IS_MVIO_ENABLED() ? AC_NULL_REG : PORTC.PIN2CTRL)); + AnalogComparator Comparator0(0, AC0, PORTDPIN2, /* No in_p1 or in_p2 */ PORTD.PIN6CTRL, (IS_MVIO_ENABLED() ? AC_NULL_REG : PORTC.PIN3CTRL), PORTD.PIN3CTRL, PORTD.PIN7CTRL, (IS_MVIO_ENABLED() ? AC_NULL_REG : PORTC.PIN2CTRL)); #endif #elif defined(ANALOG_COMP_PINS_EA) /* EA:2 ACs: P0, P1, P2, P3, P4, N0, N1, N2, N3 */ #if defined(AC0_AC_vect) #if defined(PORTE) - AnalogComparator Comparator0(0, AC0, PORTD.PIN2CTRL, PORTE.PIN0CTRL, PORTE.PIN2CTRL, PORTD.PIN6CTRL, PORTC.PIN3CTRL, PORTD.PIN3CTRL, PORTD.PIN0CTRL, PORTD.PIN7CTRL, PORTC.PIN2CTRL); + AnalogComparator Comparator0(0, AC0, PORTD.PIN2CTRL, PORTE.PIN0CTRL, PORTE.PIN2CTRL, PORTD.PIN6CTRL, PORTC.PIN3CTRL,/* EA-series has no MVIO */ PORTD.PIN3CTRL, PORTD.PIN0CTRL, PORTD.PIN7CTRL, PORTC.PIN2CTRL); #else - AnalogComparator Comparator0(0, AC0, PORTD.PIN2CTRL, AC_NULL_REG, AC_NULL_REG, PORTD.PIN6CTRL, PORTC.PIN3CTRL, PORTD.PIN3CTRL, PORTD.PIN0CTRL, PORTD.PIN7CTRL, PORTC.PIN2CTRL); + AnalogComparator Comparator0(0, AC0, PORTD.PIN2CTRL, AC_NULL_REG, AC_NULL_REG, PORTD.PIN6CTRL, PORTC.PIN3CTRL,/* EA-series has no MVIO */ PORTD.PIN3CTRL, PORTD.PIN0CTRL, PORTD.PIN7CTRL, PORTC.PIN2CTRL); #endif #endif #if defined(AC1_AC_vect) - AnalogComparator Comparator0(1, AC1, PORTD.PIN2CTRL, PORTD.PIN3CTRL, PORTD.PIN4CTRL, PORTD.PIN6CTRL, PORTC.PIN3CTRL, PORTD.PIN5CTRL, PORTD.PIN0CTRL, PORTD.PIN7CTRL, PORTC.PIN2CTRL); + AnalogComparator Comparator1(1, AC1, PORTD.PIN2CTRL, PORTD.PIN3CTRL, PORTD.PIN4CTRL, PORTD.PIN6CTRL, PORTC.PIN3CTRL,/* EA-series has no MVIO */ PORTD.PIN5CTRL, PORTD.PIN0CTRL, PORTD.PIN7CTRL, PORTC.PIN2CTRL); #endif #elif defined(ANALOG_COMP_PINS_MEGA) /* mega0:1 AC P0, P1, P2, P3, N0, N1, N2*/ #if defined(AC0_AC_vect) - AnalogComparator Comparator0(0, AC0, PORTD.PIN2CTRL, PORTD.PIN4CTRL, PORTD.PIN6CTRL, PORTD.PIN1CTRL, PORTD.PIN3CTRL, PORTD.PIN5CTRL, PORTD.PIN7CTRL); + AnalogComparator Comparator0(0, AC0, PORTD.PIN2CTRL, PORTD.PIN4CTRL, PORTD.PIN6CTRL, PORTD.PIN1CTRL,/* megaAVR 0-series has no in_p4 */ PORTD.PIN3CTRL, PORTD.PIN5CTRL, PORTD.PIN7CTRL); #endif /* Now for the tinyAVR parts | PIN | 8-pin |0/1-series AC0|2-series AC0|1+series AC0|1+series AC1|1+series AC2| @@ -68,6 +87,10 @@ AltOUT | PIN_PC6* | PIN_PC6* | PIN_PC6* | n/a | PIN_PC6* | PIN_PC6* | |IN N1 | n/a | PIN_PB4* | PIN_PB4* | PIN_PB4* | PIN_PB7* | PIN_PB6* | |IN N2 | n/a | n/a | PIN_PB0 | n/a | n/a | n/a | |OUT | PIN_PA3 | PIN_PA5 | PIN_PA5 | PIN_PA5 | PIN_PB3 | PIN_PB2 | +* * indicates pins that are not present on all parts: +* PB4, PB5 are not present on parts with less than 20 pins. +* PB6, PB7 are only present on 24-pin parts. +* AC1, AC2 are only present on the "golden 1-series" - parts with 16k+ flash have special features. */ #elif defined(ANALOG_COMP_PINS_TINY_FEW) /* P0, N0 */ @@ -145,11 +168,11 @@ AnalogComparator::AnalogComparator( : comparator_number(comp_number), AC(ac), IN0_P(in0_p), - IN2_P(in3_p), - IN3_P(in4_p), + IN3_P(in3_p), + IN4_P(in4_p), IN0_N(in0_n), - IN1_N(in2_n), - IN2_N(in3_n) { } + IN2_N(in2_n), + IN3_N(in3_n) { } #elif defined(ANALOG_COMP_PINS_EA) /*9 inputs P0, P1, P2, P3, P4, N0, N1, N2, N3 */ AnalogComparator::AnalogComparator( const uint8_t comp_number, @@ -293,12 +316,16 @@ void AnalogComparator::init() { if (comparator_number != 0) { Comparator0.reference = reference; } + #if defined(AC1) if (comparator_number != 1) { Comparator1.reference = reference; } + #endif + #if defined(AC2) if (comparator_number != 2) { Comparator2.reference = reference; } + #endif // Set DACREF AC.DACREF = dacref; @@ -410,9 +437,9 @@ void AnalogComparator::init() { } if (input_n == comparator::in_n::in0) { IN0_N = PORT_ISC_INPUT_DISABLE_gc; - } else if (input_n == comparator::in_n::in3) { + } else if (input_n == comparator::in_n::in2) { IN2_N = PORT_ISC_INPUT_DISABLE_gc; - } else if (input_n == comparator::in_n::in4) { + } else if (input_n == comparator::in_n::in3) { IN3_N = PORT_ISC_INPUT_DISABLE_gc; } #elif defined(ANALOG_COMP_PINS_DA_DB) || defined(ANALOG_COMP_PINS_MEGA) @@ -535,7 +562,7 @@ void AnalogComparator::stop(bool restorepins) { IN0_N = 0; } else if (input_n == comparator::in_n::in3) { IN2_N = 0; - } else if (input_n == comparator::in_n::in4) { + } else if (input_n == comparator::in_n::in3) { IN3_N = 0; } #elif defined(ANALOG_COMP_PINS_DA_DB) || defined(ANALOG_COMP_PINS_MEGA) diff --git a/megaavr/libraries/Comparator/src/Comparator.h b/megaavr/libraries/Comparator/src/Comparator.h index 3c68901c..bdf04b6c 100644 --- a/megaavr/libraries/Comparator/src/Comparator.h +++ b/megaavr/libraries/Comparator/src/Comparator.h @@ -18,6 +18,7 @@ #elif defined(__AVR_DD__) /* DD:1 AC: P0, P3, P4, N0, N2, N3 */ #define ANALOG_COMP_PINS_DD + #define AC_NULL_REG _SFR_MEM8(0x04B0) #define ANALOG_COMP_NO_N1 #elif defined(__AVR_EA__) /* EA:2 ACs: P0, P1, P2, P3, P4, N0, N1, N2, N3 */ @@ -91,17 +92,17 @@ namespace comparator { namespace in_p { enum inputP_t : uint8_t { in0 = 0x00, + pd2 = 0x00, #ifndef __AVR_DD__ in1 = 0x01, in2 = 0x02, #endif in3 = 0x03, - #ifdef __AVR_DD__ + pd6 = 0x03, + #if defined(__AVR_DD__) || defined(__AVR_EA__) in4 = 0x04, pc3 = 0x04, #endif - pd2 = 0x00, - pd6 = 0x03 }; }; @@ -113,16 +114,16 @@ namespace comparator { pd0 = 0x01, #endif in2 = 0x02, - #if defined(__AVR_DD__) || defined(__AVR_EA__) - in3 = 0x00, /* TBD - Will dacref change number, or will it's number be skipped? */ - #endif - dacref = 0x03, pd7 = 0x02, + #if defined(__AVR_DD__) || defined(__AVR_EA__) || defined(__AVR_EB__) + in3 = 0x03, + pc3 = 0x03, + dacref = 0x04 + #else + dacref = 0x03 + #endif }; }; - // Unknown how these will be numbered on DD-series, which has an AINN3 and DACREF - initial header release is copied verbatim from DA/DB - // and the EA-series doesn't even have that available, and I'm not privy to the pre-release preliminary datasheets that I'm sure important - // customers are poring over now. #elif defined(MEGATINYCORE) namespace in_p { enum inputP_t : uint8_t { @@ -228,7 +229,7 @@ class AnalogComparator { register8_t& in1_n, register8_t& in2_n); - #elif defined(ANALOG_COMP_PINS_DD) /*6 inputs: P0, P3, P4, N0, N2, N3 */ + #elif defined(ANALOG_COMP_PINS_DD) /*6 inputs - P1, P2, and N1 are gone, but newly added P4 and N3 are available P0, P3, P4, N0, N2, N3 */ AnalogComparator(const uint8_t comparator_number, AC_t& ac, register8_t& in0_p, @@ -237,7 +238,7 @@ class AnalogComparator { register8_t& in0_n, register8_t& in2_n, register8_t& in3_n); - #elif defined(ANALOG_COMP_PINS_EA) /*9 inputs P0, P1, P2, P3, P4, N0, N1, N2, N3 */ + #elif defined(ANALOG_COMP_PINS_EA) /*9 inputs - with 48 pins EA gets all inputs for at least one comparator. P0, P1, P2, P3, P4, N0, N1, N2, N3 */ AnalogComparator(const uint8_t comparator_number, AC_t& ac, register8_t& in0_p, diff --git a/megaavr/libraries/Comparator/src/ComparatorISR.cpp b/megaavr/libraries/Comparator/src/ComparatorISR.cpp index 6b6dbc9d..57b0f74e 100644 --- a/megaavr/libraries/Comparator/src/ComparatorISR.cpp +++ b/megaavr/libraries/Comparator/src/ComparatorISR.cpp @@ -11,7 +11,7 @@ #endif void AnalogComparator::attachInterrupt(void (*userFunc)(void), uint8_t mode) { - #if !defined(DXCORE) + #if defined(__AVR_DD__) || !defined(DXCORE) AC_INTMODE_t intmode; switch (mode) { // Set RISING, FALLING or CHANGE interrupt trigger for the comparator output diff --git a/megaavr/libraries/EEPROM/examples/eeprom_update/eeprom_update.ino b/megaavr/libraries/EEPROM/examples/eeprom_update/eeprom_update.ino index 0beb61d4..c2392565 100644 --- a/megaavr/libraries/EEPROM/examples/eeprom_update/eeprom_update.ino +++ b/megaavr/libraries/EEPROM/examples/eeprom_update/eeprom_update.ino @@ -11,6 +11,11 @@ */ #include +#if defined(megaTinyCore) + #define ANALOG_PIN PIN_PA7 +#else + #define ANALOG_PIN PIN_PD4 +#endif /* the current address in the EEPROM (i.e. which byte we're going to write to next) */ int address = 0; @@ -25,7 +30,7 @@ void loop() { * 0 to 1023 and each byte of the EEPROM can only hold a * value from 0 to 255. */ - int val = analogRead(A7) / 4; // Use A7 for example because all supported parts have it: tinyAVR, Dx, and Ex. + int val = analogRead(ANALOG_PIN) / 4; // Use A7 for example because all tinyAVRs have it /* * Update the particular EEPROM cell. diff --git a/megaavr/libraries/EEPROM/examples/eeprom_write/eeprom_write.ino b/megaavr/libraries/EEPROM/examples/eeprom_write/eeprom_write.ino index 7f440056..b7601b38 100644 --- a/megaavr/libraries/EEPROM/examples/eeprom_write/eeprom_write.ino +++ b/megaavr/libraries/EEPROM/examples/eeprom_write/eeprom_write.ino @@ -6,12 +6,18 @@ */ #include +#if defined(megaTinyCore) + #define ANALOG_PIN PIN_PA7 +#else + #define ANALOG_PIN PIN_PD4 +#endif + /* the current address in the EEPROM (i.e. which byte we're going to write to next) */ int addr = 0; void setup() { - // initialize the LED pin as an output - skip if LED_BUILTIN is defined as PIN_PA3 and using external clock source (an invalid configuration in practice!). We test for this to ensure that the sketch will compile successfully and can be used for CI testing + // initialize the LED pin as an output - skip if LED_BUILTIN is defined as the CLKIN pin and using external clock source (an invalid configuration in practice!). We test for this to ensure that the sketch will compile successfully and can be used for CI testing #if CLOCK_SOURCE != 2 || LED_BUILTIN != PIN_PA3 pinMode(LED_BUILTIN, OUTPUT); #endif @@ -23,7 +29,7 @@ void loop() { * value from 0 to 255. */ - int val = analogRead(A7) / 4; // Use A7 for example because all supported parts have it: tinyAVR, Dx, and Ex. + int val = analogRead(ANALOG_PIN) / 4; /* Write the value to the appropriate byte of the EEPROM. these values will remain there when the board is diff --git a/megaavr/libraries/EEPROM/src/EEPROM.h b/megaavr/libraries/EEPROM/src/EEPROM.h index 11556e96..04683d5a 100644 --- a/megaavr/libraries/EEPROM/src/EEPROM.h +++ b/megaavr/libraries/EEPROM/src/EEPROM.h @@ -72,7 +72,7 @@ struct EERef { } EERef &operator = (uint8_t in) { - #ifdef MEGATINYCORE + #if defined(MEGATINYCORE) || defined(__AVR_EA__) || defined(__AVR_EB__) // I see no reason why eeprom_write_byte() won't corrupt EEPROM if an ISR tries to write at the wrong instant. The window is 1 clock, but not 0 uint16_t adr = (uint16_t)MAPPED_EEPROM_START + (index & EEPROM_INDEX_MASK); __asm__ __volatile__( diff --git a/megaavr/libraries/Event/examples/Read_event_settings/Read_event_settings.ino b/megaavr/libraries/Event/examples/Read_event_settings/Read_event_settings.ino index 72d951b8..3efdde33 100644 --- a/megaavr/libraries/Event/examples/Read_event_settings/Read_event_settings.ino +++ b/megaavr/libraries/Event/examples/Read_event_settings/Read_event_settings.ino @@ -18,45 +18,45 @@ |***********************************************************************/ #include - +#define MYSERIAL Serial // Function to print information about the passed event void print_event_info(Event &my_event) { - Serial.printf("This is event channel no. %d\n", my_event.get_channel_number()); - Serial.printf("This channel uses generator no. 0x%02x, which you can find in Event.h\n", my_event.get_generator()); + MYSERIAL.printf("This is event channel no. %d\n", my_event.get_channel_number()); + MYSERIAL.printf("This channel uses generator no. 0x%02x, which you can find in Event.h\n", my_event.get_generator()); } void print_user_info(user::user_t my_user) { // Event::get_user_channel() returns -1 if the user isn't connected to any event generator - Serial.printf("User 0x%02x is connected to event channel no. %d\n\n", my_user, Event::get_user_channel(my_user)); + MYSERIAL.printf("User 0x%02x is connected to event channel no. %d\n\n", my_user, Event::get_user_channel_number(my_user)); } void setup() { - Serial.begin(115200); // Initialize hardware serial port - - Event2.set_generator(gen2::pin_pa1); // Set pin PA1 as event generator - #if defined(MEGATINYCORE) - #if MEGATINYCORE_SERIES == 2 - Event3.set_generator(gen3::pin_pa6); // Set pin PA6 as event generator - #else - Event3.set_generator(gen4::pin_pb1); // PB1 as the generator for this channel - #endif + MYSERIAL.begin(115200); // Initialize hardware serial port + + Event0.set_generator(gen0::pin_pa6); // Set pin PA6 as event generator + // For more information about EVOUT, see the PORTMUX section in the datasheet - Event2.set_user(user::evouta_pin_pa2); // Set EVOUTE as event user - Event3.set_user(user::evoutb_pin_pb2); // Set EVOUTF as event user + Event0.set_user(user::evouta_pin_pa2); // Set EVOUTA as event user // Start event channels + Event0.start(); + #if !defined(MEGATINYCORE) || _AVR_PINCOUNT > 8 + // again for the second channel, if we have + Event2.set_generator(gen1::pin_pa4); // Set pin PA4 as event generator + Event2.set_user(user::evoutb_pin_pb2); // Set EVOUTB as event user Event2.start(); - Event3.start(); + #endif } void loop() { - // Print info about Event4 and its event user - print_event_info(Event2); - print_user_info(user::evouta_pin_pa2); + // Print info about Event0 and its event user + print_event_info(Event0); + print_user_info(user::evoutb_pin_pa2); - // Print info about Event3 and its event user - print_event_info(Event3); + #if !defined(MEGATINYCORE) || MEGATINYCORE_SERIES == 2 + // Print info about Event2 and its event user + print_event_info(Event2); print_user_info(user::evoutb_pin_pb2); - + #endif delay(5000); } diff --git a/megaavr/libraries/Event/src/Event_parts.h b/megaavr/libraries/Event/src/Event_parts.h index c38c4bac..76ea1684 100644 --- a/megaavr/libraries/Event/src/Event_parts.h +++ b/megaavr/libraries/Event/src/Event_parts.h @@ -16,7 +16,7 @@ // Features present on all generator channels namespace event { -#if !(defined(MEGATINYCORE) || defined(PORT_EVGEN0SEL)) //if neither tiny nor EA or beyond, here's the list +#if !defined(MEGATINYCORE) && !defined(PORT_EVGEN0SEL_gm) //if neither tiny nor EA or beyond, here's the list namespace gen { enum generator_t : uint8_t { disable = 0x00, @@ -663,54 +663,54 @@ namespace event { ccl2_event_b = 0x05, ccl3_event_a = 0x06, ccl3_event_b = 0x07, - adc0_start = 0x0C, + adc0_start = 0x08, #if defined(PIN_PA2) // not on 14-pin ones. - evouta_pin_pa2 = 0x0D, + evouta_pin_pa2 = 0x09, #endif - evoutc_pin_pc2 = 0x0F, + evoutc_pin_pc2 = 0x0A, #if defined(PIN_PD2) // only on 28 or 32 pin ones. - evoutd_pin_pd2 = 0x10, + evoutd_pin_pd2 = 0x0B, #endif #if defined(PIN_PF2) // only on 32-pin ones. - evoutf_pin_pf2 = 0x12, -#endif - usart0_irda = 0x14, - usart1_irda = 0x15, - tca0 = 0x1A, - tca0_cnt_a = 0x1A, - tca0_cnt_b = 0x1B, - tcb0 = 0x1E, - tcb0_capt = 0x1E, - tcb0_cnt = 0x1F, - tcb1 = 0x20, - tcb1_capt = 0x20, - tcb1_cnt = 0x21, + evoutf_pin_pf2 = 0x0C, +#endif + usart0_irda = 0x0D, + usart1_irda = 0x0E, + tca0 = 0x0F, + tca0_cnt_a = 0x0F, + tca0_cnt_b = 0x10, + tcb0 = 0x11, + tcb0_capt = 0x11, + tcb0_cnt = 0x12, + tcb1 = 0x13, + tcb1_capt = 0x13, + tcb1_cnt = 0x14, #if defined(TCB2) - tcb2 = 0x22, - tcb2_capt = 0x22, - tcb2_cnt = 0x23, + tcb2 = 0x15, + tcb2_capt = 0x15, + tcb2_cnt = 0x16, #endif - tcd0_in_a = 0x28, - tcd0_in_b = 0x29, + tcd0_in_a = 0x17, + tcd0_in_b = 0x18, #if defined(PIN_PA7) // not on 14-pin ones. - evouta_pin_pa7 = 0x8D, + evouta_pin_pa7 = 0x89, #endif - evoutd_pin_pd7 = 0x90, -#if defined(PIN_PF7) // only on 32-pin ones. - evoutf_pin_pf7 = 0x92, + evoutd_pin_pd7 = 0x8B, +#if defined(PIN_PF7) + evoutf_pin_pf7 = 0x8C, #endif #endif }; }; - // END NON-TINY ENUMS // - #elif defined(PORT_EV0GENSEL_gm) + // End of Dx series - start of Ex-series // + #elif defined(PORT_EVGEN0SEL_gm) // Start EVGEN block // Parts with uniform event channels, eg, the future EA-series parts. We may be able to use this code for every Ex-series part, or will need only trivial changes. namespace gen { enum generator_t : uint8_t { disable = 0x00, off = 0x00, updi_synch = 0x01, -#ifdef(MVIO) +#if defined(MVIO) mvio_ok = 0x05, #endif rtc_ovf = 0x06, @@ -721,27 +721,27 @@ namespace event { ccl1_out = 0x11, ccl2_out = 0x12, ccl3_out = 0x13, -#ifdef (CCL_LUT4CTRLA) // I fully expect a 6-LUT, 64 pin Ex-series part +#if defined(CCL_LUT4CTRLA) // I fully expect a 6-LUT, 64 pin Ex-series part ccl4_out = 0x14, ccl5_out = 0x15, #endif -#ifdef (CCL_LUT6CTRLA) // Any ATmega2560 replacement would probably have at least 8 LUTs +#if defined(CCL_LUT6CTRLA) // Any ATmega2560 replacement would probably have at least 8 LUTs ccl6_out = 0x16, ccl7_out = 0x17, #endif -#ifdef (CCL_LUT8CTRLA) // Maybe even more +#if defined(CCL_LUT8CTRLA) // Maybe even more ccl8_out = 0x18, ccl9_out = 0x19, #endif -#ifdef (CCL_LUT10CTRLA) +#if defined(CCL_LUT10CTRLA) ccl10_out = 0x1A, ccl11_out = 0x1B, #endif -#ifdef (CCL_LUT12CTRLA) +#if defined(CCL_LUT12CTRLA) ccl2_out = 0x1C, cc13_out = 0x1D, #endif -#ifdef (CCL_LUT14CTRLA) +#if defined(CCL_LUT14CTRLA) ccl14_out = 0x1E, ccl15_out = 0x1F, // Hey, I can dream can't I? Can you imagine what you could do with 4 TCA's 16 event channels, 16 LUTs and a 3-phase TCD? @@ -753,10 +753,10 @@ namespace event { ac1_out = 0x21, #endif #if defined(ac2) // An Ex with three AC would not surprise me - ac2_out = 0x22, + ac2_out = 0x21, #endif #if defined(ac3) // An Ex with fourAC would not surprise me - ac3_out = 0x23, + ac3_out = 0x21, #endif adc0_ready = 0x24, adc0_sample = 0x25, @@ -765,7 +765,7 @@ namespace event { zcd1_out = 0x32, #endif #if defined(ZCD1) - zcd1_out = 0x33, + zcd1_out = 0x32, #endif #if defined(ZCD2) zcd2_out = 0x32, @@ -804,26 +804,24 @@ namespace event { // Becayse the uniform EVSYS channels introduced with the EA leave plenty of holes, it's likely that that part would have it's channels nubered similarly, // with it's extra ports porth_evgen0 = 0x4E, - porth_evgen1 = 0x4E, -#endif -#if defined(PORTI) // not clear how the remaining ports would be handled, but that's probably what the 0x5_ range is earmarked for. - porth_evgen0 = 0x50, - porth_evgen1 = 0x51, + porth_evgen1 = 0x4F, #endif -#if defined(PORTJ) - porti_evgen0 = 0x52, - porti_evgen1 = 0x53, +#if defined(PORTJ) // not clear how the remaining ports would be handled, but that's probably what the 0x5_ range is earmarked for. + portj_evgen0 = 0x50, + portj_evgen1 = 0x51, #endif #if defined(PORTK) - portk_evgen0 = 0x54, - portk_evgen1 = 0x55, + portk_evgen0 = 0x52, + portk_evgen1 = 0x53, #endif -#if defined(PORTL) // or ar least up to 0x08. I havent'y seen anything beyond PORTL on an AVR. - portl_evgen0 = 0x56, - portl_evgen1 = 0x57, +#if defined(PORTL) + portl_evgen0 = 0x54, + portl_evgen1 = 0x55, #endif usart0_xck = 0x60, +#if defined(USART1) usart1_xck = 0x61, +#endif #if defined(USART2) usart2_xck = 0x62, #endif @@ -839,23 +837,28 @@ namespace event { #if defined(USART6) usart6_xck = 0x66, #endif -#if defined(USART7) // The highest number of USARTs that naturally fit this numbering coincides with somewhere around what we'd expect on a 100 pin part. - usart7_xck = 0x67, //which would have... maybe 86 I/O pins if we allow for ample power and ground pins., meaning 4 new ports, adding at most 32 new pins. - // If they were willing to go as far as extending the architecture, there is still room enough for just over 1500 instructions. 512 of them could - with compiler support - provide 4 more first class ports! +#if defined(USART7) // The highest number of USARTs that naturally fit this numbering coincides with the maximum plausible number on a 100 pin chip + usart7_xck = 0x67, #endif spi0_sck = 0x68, #if defined(SPI1) // I fully expect to see Ex-series parts with more SPI ports and higher pincounts. spi1_sck = 0x69, #endif -#if defined(TCA1) +#if defined(TCA0) tca0_ovf_lunf = 0x80, tca0_hunf = 0x81, tca0_cmp0 = 0x84, tca0_cmp1 = 0x85, tca0_cmp2 = 0x86, #endif -#if defined(TCA1) // EA-series parts have 2 TCA's, but if they go to very low pincounts with an "ED-series" which will probably not be called that - // these would probably lose the second timer. +#if defined(TCE0) + tce0_ovf = 0x80, /* Timer/Counter E0 overflow */ + tce0_cmp0 = 0x84, /* Timer/Counter E0 compare 0 */ + tce0_cmp1 = 0x85, /* Timer/Counter E0 compare 1 */ + tce0_cmp2 = 0x86, /* Timer/Counter E0 compare 2 */ + tce0_cmp3 = 0x87, /* Timer/Counter E0 compare 3 */ +#endif +#if defined(TCA1) // I fully expect to see Ex-series parts with only 1 TCA, in low pincounts. tca1_ovf_lunf = 0x88, tca1_hunf = 0x89, tca1_cmp0 = 0x8C, @@ -903,9 +906,17 @@ namespace event { tcd0_cmpaset = 0xB1, tcd0_cmpbset = 0xB2, tcd0_progev = 0xB3, +#endif +// Saving room for TCD1? +#if defined(TCF0) + tcf0_ovf = 0xB8, /* Timer/Counter F0 Overflow */ + tcf0_cmp0 = 0xB9, /* Timer/Counter F0 compare 0 */ + tcf0_cmp1 = 0xBA /* Timer/Counter F0 compare 1 */ #endif }; }; + /* Done with plausible generators */ + #if defined(__AVR_EA__) //these are specific to the family, namespace user{ enum user_t : uint8_t { @@ -927,31 +938,47 @@ namespace event { #if defined(PIN_PE2) evoute_pin_pe2 = 0x0D, #endif +#if defined(PIN_PF2) evoutf_pin_pf2 = 0x0E, +#endif usart0_irda = 0x0F, +#if defined(USART2) usart1_irda = 0x10, +#endif +#if defined(USART2) usart2_irda = 0x11, - tca0 = 0x11, - tca0_cnt_a = 0x11, +#endif +#if defined(TCA0) + tca0 = 0x12, + tca0_cnt_a = 0x12, tca0_cnt_b = 0x13, +#endif +#if defined(TCA1) tca1 = 0x14, tca1_cnt_a = 0x14, tca1_cnt_b = 0x15, +#endif +#if defined(TCB0) tcb0 = 0x16, tcb0_capt = 0x16, - tcb0_ovf = 0x17, + tcb0_cnt = 0x17, +#endif +#if defined(TCB1) tcb1 = 0x18, tcb1_capt = 0x18, - tcb1_ovf = 0x19, + tcb1_cnt = 0x19, +#endif +#if defined(TCB2) tcb2 = 0x1A, tcb2_capt = 0x1A, - tcb2_ovf = 0x1B, + tcb2_cnt = 0x1B, +#endif #if defined(TCB3) tcb3 = 0x1C, tcb3_capt = 0x1C, - tcb3_ovf = 0x1D, + tcb3_cnt = 0x1D, #endif - // "Unofficial" user generators. Uses EVOUT, but swaps the output pin using PORTMUX + // "Unofficial" users. Uses EVOUT, but swaps the output pin using PORTMUX evouta_pin_pa7 = 0x89, #if defined(PIN_PB7) evoutb_pin_pb7 = 0x8A, @@ -963,19 +990,68 @@ namespace event { #if defined(PIN_PE7) evoute_pin_pe7 = 0x8D, #endif -#if defined(PIN_PE7) - evoutf_pin_pf7 = 0x8E, + evoutf_pin_pf7 = 0x8E + }; + }; + #elif defined(__AVR_EB__) + namespace user{ + enum user_t : uint8_t { + ccl0_event_a = 0x00, + ccl0_event_b = 0x01, + ccl1_event_a = 0x02, + ccl1_event_b = 0x03, + ccl2_event_a = 0x04, + ccl2_event_b = 0x05, + ccl3_event_a = 0x06, + ccl3_event_b = 0x07, + adc0_start = 0x08, +#if defined(PIN_PA2) + evouta_pin_pa2 = 0x09, +#endif +#if defined(PIN_PC2) + evoutc_pin_pc2 = 0x0A, +#endif +#if defined(PIN_PD2) + evoutd_pin_pd2 = 0x0B, #endif +#if defined(PIN_PF2) + evoutf_pin_pf2 = 0x0C, +#endif + usart0_irda = 0x0D, + tcae = 0x0E, + tce0_cnt_a = 0x0F, + tce0_cnt_b = 0x10, +#if defined(TCB0) + tcb0 = 0x11, + tcb0_capt = 0x11, + tcb0_cnt = 0x12, +#endif +#if defined(TCB1) + tcb1 = 0x13, + tcb1_capt = 0x13, + tcb1_cnt = 0x14, +#endif + tcf0_cnt = 0x15, /* TCF0 Clock Event */ + tcf0_act = 0x16, /* TCF0 Action Event */ + wexa = 0x17, /* WEX Event A */ + wexb = 0x18, /* WEX Event B */ + wexc = 0x19, /* WEX Event C */ +#if defined(PIN_PA7) + // "Unofficial" users. Uses EVOUT, but swaps the output pin using PORTMUX + evouta_pin_pa7 = 0x89, +#endif + evoutd_pin_pd7 = 0x8C, + evoutf_pin_pf7 = 0x8E }; }; - #endif // defined (__AVR_EA__) + #endif // defined (__AVR_EB__) #elif MEGATINYCORE_SERIES == 2 namespace gen { enum generator_t : uint8_t { disable = 0x00, - updi_synch = 0x1, - rtc_ovf = 0x6, - rtc_cmp = 0x7, + updi_synch = 0x01, + rtc_ovf = 0x06, + rtc_cmp = 0x07, ccl0_out = 0x10, ccl1_out = 0x11, ccl2_out = 0x12, diff --git a/megaavr/libraries/Logic/README.md b/megaavr/libraries/Logic/README.md index 6351702c..35023224 100644 --- a/megaavr/libraries/Logic/README.md +++ b/megaavr/libraries/Logic/README.md @@ -111,12 +111,36 @@ The correct order for for initialization is: 2. Attach any interrupts if using attachInterrupt. Note that interrupts may also be defined manually - if this is done, you must avoid calling LogicN.attachInterrupt and LogicN.detachInterrupt on any logic block, and write to the appropriate registers to enable them. Interrupts defined this way, particularly if short and simple, will execute much faster. Refer to the datasheet for more information. 2. call `LogicN.init()` to write those properties to the CCL registers. -The correct procedure for modifying the configuration of one or more logic blocks: +The correct procedure for modifying the configuration of one or more logic blocks is impacted by a significant erratum on most extant silicon. Currently, only the AVR DD-series is spared. + +**When ERRATA_CCL_PROTECTION is undefined, or defined and -1 or a number higher than SYSCFG.REVID:** 1. Set the properties as required for all logic blocks being changed. 2. Call `Logic::stop()`. 3. Call `LogicN.init()` on all logic blocks that have been changed. 4. Call `Logic::start()` to restart the CCL as a whole. +**When ERRATA_CCL_PROTECTION is defined, and is 0 or a number lower than SYSCFG.REVID** this behavior has been fixed, and you do not need to shut down the CCL entirely +1. Set the properties as required for logic block being changed. +2. Call `LogicN.init()` on all that logic block. This will briefly shut down only that particular logic block, just long enough to reconfigure it. Other logic blocks will continue to function. +3. Repeat if configuring more than one. You may set all the properties of multiple blocks and then call init in succession, or not, as you please. + +NOTE: You may wish to follow the first process even on parts without this erratum - if all of your LUTs are interdependent, you probably want them to come back on simultaneously. + +You may test for this as shown below: +```c++ +// assumes you've already set the properties +if (checkErrata(ERRATA_CCL_PROTECTION)) { + Logic::stop(); //All other logic blocks briefly disabled! + Logic1.init(); + Logic2.init(); + Logic::start(); // all logic blocks re-enabled. +} else { + Logic1.init(); // Only logic block 1 disabled while this runs. + Logic2.init(); // Only logic block 2 disabled while this runs. +} +``` + + ## Properties ### enable @@ -219,13 +243,16 @@ Notes specific to ATtiny 0/1-series: * If you need feedback input from an odd-numbered logic block, use the event system for that. * Not all pin inputs are available on all parts (see table above). The event system can be used to bring in input from other pins. * CCL0's IN0 pin is on PA0, which is nominally the UPDI pin. This may limit the usefulness of CCL0 on the ATtiny parts (though it may work as long as the input cannot be mistaken for a UPDI activation command); configuring UPDI as GPIO prevents further programming via UPDI except via HV programming. One can always use the event system to substitute another input for IN0; This is demonstrated in the three input example. -* Only the ATtiny1614, 1616, 1617, 3216, and 3217 have TCB1, AC1, and AC2. -* **Errata warning** Many parts in circulation are impacted by an errata (though not all - some never had it, while the 32k parts have gotten a die rev that fixes it. On effected parts, the link input does not work unless pin output of the other logic block is enabled. Check the applicable errata and datasheet clarification document from Microchip to see if your part is impacted. -* **Compatibility warning** These were the first AVRs with CCL. They made some decisions that they realized weren't such a good idea after all and changed for parts released more recently. Most importantly, for TCBs and ACs, as well as USARTs, the peripheral number used is the input number. SPI on these parts makes MISO available supposedly. Later parts do not. Later parts also only make USART TXD available - though you can get XCK from the event system. +* Among tinyAVR 1-series Only the ATtiny1614, 1616, 1617, 3216, and 3217 have TCB1, AC1, and AC2. All tiny 2-series have TCB1, but they never have more than one AC. +* DA/DB have 3 AC's, EA 2, and the DD, DU and EB have one. +The two final limits add further chaos to the distributions of numeric values for users and generators - the two seem to have had separate algorithms for determining when to leave holes for absent peripherals, and when to fill them in. They *really* seem to like filling in the holes in the generator lists, leaving me wondering what it is abouut the length of the list with such a high cost... + +* **Errata warning** Many parts in circulation are impacted by an errata (though not all - some never had it, while the 32k parts have gotten a die rev that fixes it. On effected parts, the link input does not work unless pin output of the other logic block is enabled. Check the applicable errata and datasheet clarification document from Microchip to see if your part is impacted. Other parts have a +* **Compatibility warning** The tinyAVR 0/1's were the first AVRs with CCL. They made some decisions that they realized weren't such a good idea after all and changed for parts released more recently. Most importantly, for TCBs and ACs, as well as USARTs, the peripheral number used is the input number. SPI on these parts makes MISO available supposedly. Later parts do not. Later parts also only make USART TXD available - though you can get XCK from the event system. * USART option will use XCK on input 0, TXD on input 1, and is not valid for input 2. * MISO is (supposedly) available as an input when SPI is used as the input. * Parts with two TCBs or three ACs give them their own channel on 0/1-series. Obviously there wouldn't be enough channels for this if it were done on a Dx with 5 TCBs, and 3 ACs. The other parts that have multiples of these (2-series and Dx) have one channel for this, and the input number selects which one is used, but only the first two can be used. -* The tinyAVR 0/1-series datasheets refer to the event channels as 0 and 1. On all subsequent parts, they are referred to as A and B. The Logic library always accepts both, though we recommend using event_a/event_b everywhere, as it is clear that that is the convention that Microchip chose to settle on. +* The tinyAVR 0/1-series datasheets refer to the event channels as 0 and 1. On all subsequent parts, they are referred to as A and B. The Logic library always accepts both, though we recommend using `event_a`/`event_b`everywhere, as it is clear that that is the convention that Microchip chose to settle on. #### Accepted values for tinyAVR 2-series ``` c++ @@ -337,9 +364,9 @@ Accepted values: ```c++ logic::clocksource::clk_per; // Clock from the peripheral clock (ie, system clock) logic::clocksource::in2; // Clock from the selected input2; it is treated as a 0 in the truth table. -logic::clocksource::oschf; // Clock from the **unprescaled** internal HF oscillator. Same as clk_per if system clock not prescaled. tinyAVR 2-series (and Dx-series) -logic::clocksource::osc32k; // Clock from the internal 32.768 kHz oscillator - tinyAVR 2-series only (and Dx-series) -logic::clocksource::osc1k; // Clock from the internal 32.768 kHz oscillator prescaled by 32 - tinyAVR 2-series only (and Dx-series) +logic::clocksource::oschf; // Clock from the **unprescaled** internal HF oscillator. Same as clk_per if system clock not prescaled, unless using external clock or crystal +logic::clocksource::osc32k; // Clock from the internal 32.768 kHz oscillator - tinyAVR 2-series and Dx-series only +logic::clocksource::osc1k; // Clock from the internal 32.768 kHz oscillator prescaled by 32 - tinyAVR 2-series and Dx-series only ``` @@ -362,14 +389,14 @@ logic::edgedetect::enable; // Edge detection used ``` ### sequencer -Property controlling the "sequencer" for this pair of logic blocks - these are latches or flip-flops which will remember their state. There is 1 sequencer per 2 logic blocks; each logic block controls one of the two inputs to a flip flop or latch; *this property is ignored for the odd-numbered logic blocks*. Flip-flops are clocked from the same clock source as the even logic block. Latches are asynchronous. **The output of the sequencer will for all purposes replace the output of the even logic block** +Property controlling the "sequencer" for this pair of logic blocks - these are latches or flip-flops which will remember their state. There is 1 sequencer per 2 logic blocks; each logic block controls one of the two inputs to a flip flop or latch; **this property is ignored for the odd-numbered logic blocks**. Flip-flops are clocked from the same clock source as the even logic block. Latches are asynchronous. **The output of the sequencer will for all purposes, including link, feedback, pin and event outputs, replace the output of the even logic block** Accepted values: ```c++ logic::sequencer::disable; // No sequencer connected logic::sequencer::d_flip_flop; // D flip flop sequencer connected logic::sequencer::jk_flip_flop; // JK flip flop sequencer connected -logic::sequencer::d_latch; // Gated D latch sequencer connected - note that on most tinyAVR 0/1-series parts, this straight up doesn't work. (though recently produced 32k 1-series parts have it fixed). See the relevant errata. +logic::sequencer::d_latch; // Gated D latch sequencer connected (broken on many early modern AVRs, see the errata) logic::sequencer::rs_latch; // RS latch sequencer connected ``` @@ -390,19 +417,19 @@ Below is shown in the 4 left columns an RS latch, and in the right 4, a D latch Hence truth tables are 0xB2 and 0xB8, and both of these leave the odd LUT available. Of course, if you need a logic block to come up with the inputs to set and reset, this is less useful. #### D Flip-flop -The D-type fiip-flow outputs a 0 or a 1, and retains it's value unless told otherwise. The inputs consist of G (maybe for gate?) and D (data?) input. As long as G is low, nothing will change, When G is high, each rising edge will latch the value on the D line to the output. +The D-type fiip-flow outputs a 0 or a 1, and retains it's value unless told otherwise. The inputs consist of G and D input. As long as G is low, nothing will change, When G is high, each rising edge will latch the value on the D line to the output. As I understand, D is derived from "Data" and G from "Gate", in the sense that it opens the gate and allows new data in. The even LUT drives the D input, and the odd LUT drives the G input. #### JK Flip-flop -The JK-type fiip-flow outputs a 0 or a 1, and retains it's value unless told otherwise. The inputs consist of J (set) and K (clear). On the rising edge of the clock, if J is high and K is not, the output will be set to 1. If K is high and J is low, the output will be set to 0, and if both are high, it will toggle the output. In all cases, the value will be retained until the next rising edge of the lock occurs while J and K are set to values that instruct it to change. +The JK-type fiip-flow outputs a 0 or a 1, and retains it's value unless told otherwise. The inputs consist of J (set) and K (clear). On the rising edge of the clock, if J is high and K is not, the output will be set to 1. If K is high and J is low, the output will be set to 0, and if both are high, it will toggle the output. In all cases, the value will be retained until the next rising edge of the lock occurs while J and K are set to values that instruct it to change. The names of the lines are as universal on flipflops are they are nonsensical - J and K? Your guess is as good as mine where they came from #### D Latch This is the unclocked equivalent of D flip-flop - again, there is a D and a G input. However, here, there is no clock. Asynchronously, whenever G is high, the output is set to D, and whenever G is low, the output doesn't change. #### RS Latch -This, again, is the unclocked version of the JK flip-flop. This one more sensibly names the inputs S and R for Set and Reset. Because there is no clock, the behavior when both R and R are high is not defined (this behavior is part of deal when you use an RS latch, integrated into a CCL like this or as a discrete component). Otherwise, if the S line is high, it is set to 1, and if the R line is high, it is set to 0 +This, again, is the unclocked version of the JK flip-flop. This one more sensibly names the inputs S and R for Set and Reset. Because there is no clock, the behavior when both R and S are high is not defined (this behavior is part of deal when you use an RS latch, integrated into a CCL like this or as a discrete component). Otherwise, if the S line is high, it is set to 1, and if the R line is high, it is set to 0 | LUT | D | G | J | K | R | S | |------|---|---|---|---|---|---| @@ -452,7 +479,7 @@ Logic0.truth = 0xF0; ## Logic Methods ### init() -Method for initializing a logic block; the settings you have previously configured will be applied and pins configured as requested at this time only. +Method for initializing a logic block; the settings you have previously configured will be applied and pins configured as requested *only* when init() is called. See the section below on reconfiguring. #### Usage ```c++ @@ -481,16 +508,24 @@ Logic::stop(); // Stop CCL Method for enabling interrupts for a specific block. Valid arguments for the third parameters are `RISING`, `FALLING` and `CHANGE`. This method isn't available on tinyAVR 0/1-series as these parts cannot generate an interrupt from the CCL blocks. -All forms of attachInterrupt, everywhere, are fundamentally evil, because they add a several microsecond overhead to the ISR simply because there is a call to a non-inlinable function; + +All forms of attachInterrupt, everywhere, are fundamentally evil, because they add a several microsecond overhead to the ISR simply because there is a call to a non-inlinable function, but the attach method is more familiar to most Arduino users. #### Usage ```c++ -Logic0.attachInterrupt(blinkLED, RISING); // Runthe blinkLED function when the putput goes high +Logic0.attachInterrupt(blinkLED, RISING); // Runthe blinkLED function when the output goes high void blinkLED() { digitalWrite(myLedPin, CHANGE); } + +void setup() { + // other configuration and initialization of Logic0 needed + Logic0.attachInterrupt(blinkLED, RISING); // Runthe blinkLED function when the putput goes high + // and you'll need to do logic::start when you're ready for it to be enabled. +} + ``` @@ -502,39 +537,212 @@ This method isn't available on tinyAVR 0/1-series. ```c++ Logic0.detachInterrupt(); // Disable interrupts for block 0 ``` +### Advanced interrupts +New in 1.5.0, it is possible to use interrupts "manually" while still using the Logic library. This results in smaller, faster executing interrupts. + +You must declare a function using the ISR macro, like so: +```c++ +ISR(CCL_CCL_vect) { + // It's best if interrupts run quickly and are simple (don't involve accessing scores of variables). + // Everyday things like making a function call can dramatically slow an ISR. Ideally, you should not + // call any function here that can't be inlined. Functions that are only called once will be inlined, + // as will certain "always_inline" functions, which include the digitalWriteFast() and similar. + // digitalWrite 1) won't be inlined since it's almost certainly used elsewhere, and 2) is unbelivably slow. + // But before we get to that, you may have noticed that CCL_INT_vect doesn't have the number of the LUT in it. + // If you've worked with manual pin interrupts you know what that means. There's one interrupt, and if there's + // more than one CCL interrupt enabled, you have to read the flags and see which interrupt it is. + // Note that any typo or mistake of any sort in the vector name - amazingly - will not be treated as a compile error. + // Just a warning. When the interrupt fires the application will uncerimoniously reset. + uint8_t flags=CCL.INTFLAGS; + // bits 0 - 5 correspond to LUT0 - LUT5, as you could have guessed. + // eg, 0bRR543210 - where R is a reserved bit, and the numbers are the number of the LUT corresponding to that bit. + // You also need to clear the intflags - that isn't done automatically + // If interrupts may fire in very rapid succession the timing of when you clear the flags could matter. + // You should always read the flags first thing. Writing them immediately minimizes the possibility + // of an repeated interrupt condition being missed because you hadn't cleared the flag yet, but it also increases + // the potential for something like switch bounce to land you in the interrupt multiple times, and in extreme + // situation, can result in the code being locked in the interrupt, because it will re-trigger immediately after + // returning, It's probably smarter to only clear the intflags at the very end. This is done by writing a 1 to each + // flag you are clearing, which typically means all the ones you saw were set at the start of the ISR. + // DO NOT read the flags a second time - use the value you read earlier. That way if another logic block's interrupt + // triggered, it would naturally be triggered as soon as you left this interrupt. The interrupt would run twice, though + // all interrupt conditions that occurred would be handled. + // Anyway, back to the body of the ISR + // Assuming that your LED pin is a constant, this is the preferred way to toggle a pin: + digitalWriteFast(myLedPin, CHANGE); + // And now we clear the intflags... + CCL.INTFLAGS = flags; + // If you forget to do this, the interrupt will run continually. After each interrupt returns, a single instruction will be run before the interrupt runs again, generating the appearance of the sketch running very slowly. +} +``` + +Assuming you have a CCL interrupt defined, you can enable it by using the `CCL.INTCTRL0` (LUT0-LUT3) and `CCL.INTCTRL1` (LUT4, LUT5) registers. +These registers are structured like: + +```text +INTCTRL0: +0b44332211 +INTCTRL1 (48/64 pin DA/DB only): +0bRRRR5544 + +Where R indicates a reserved bit. + +The two bits per LUT allow the interrupt to be triggered on +00 - neither/disable interrupt +01 - rising +10 - falling +11 - rising or falling + +``` + +## Latch-without-sequencer +The available sequencer options, unfortunately, are capable of only slightly more than what can be done with just the even block alone, using feedback. An example of this (2 on DxCore) shows a different input ordering. The comments in the LatchNoSeq example provide a bit more information. You can do many tasks that would at first blush look like a job for the latches by simply setting one input as feedback, and the other two to your latch inputs. + +##### RS-latch w/out sequencer +Input0 is Set +Input1 is Reset +Input2 is Feedback + +| 2 | 1 | 0 | Out | +|---|---|---|-----| +| 0 | 0 | 0 | 0 | +| 0 | 0 | 1 | 1 | +| 0 | 1 | 0 | 0 | +| 0 | 1 | 1 | 0 | +| 1 | 0 | 0 | 1 | +| 1 | 0 | 1 | 1 | +| 1 | 1 | 0 | 0 | +| 1 | 1 | 1 | 1 | + +Truth = 0b10110010 = 0xB2; + +Note that here you have the power to decide what happens in the event that the illegal state where R and S are both high occurs. Here I have it maintain the current state. +It's also fine to have it go high or low on the "S & R = 1" state, with truthtables of 0b10111010 = 0xBA, or 0b00110010 = 0x32 respectively. But make it toggle, ie, truth table of 0b00111010 = 0x3A and it will instead oscillate extremely rapidly (though it could be slowed down with the synchronizer or filter, that still isn't particularly useful ) + +##### D-type latch w/out sequencer +Input0 is D +Input1 is G +Input2 is Feedback + +| 2 | 1 | 0 | Out | +|---|---|---|-----| +| 0 | 0 | 0 | 0 | +| 0 | 0 | 1 | 0 | +| 0 | 1 | 0 | 0 | +| 0 | 1 | 1 | 1 | +| 1 | 0 | 0 | 1 | +| 1 | 0 | 1 | 1 | +| 1 | 1 | 0 | 0 | +| 1 | 1 | 1 | 1 | + +Hence truth tables are 0xB2 and 0xB8, and both of these leave the odd LUT available. Of course, if you need a logic block to come up with the inputs to set and reset, this is less useful. + + ## Reconfiguring -There are TWO levels of "enable protection" on the CCL hardware. According to the Silicon Errata, only one of these is intended. As always, it's anyone's guess when or if this issue will be corrected in a future silicon rev, and if so, on which parts (it would appear that Microchip only became aware of the issue after the Dx-series parts were released - although it impacts all presently available parts, it is only listed in errata updated since mid-2020). Users are advised to proceed with use of workarounds, rather than delay work in the hopes of corrected silicon. The intended enable-protection is that a given logic block cannot be reconfigured while enabled. This is handled by `init()` - you can write your new setting to a logic block, call `LogicN.init()` and it will briefly disable the logic block, make the changes, and re-enable it. +There are TWO levels of "enable protection" on the CCL hardware except on the newest parts . According to the Silicon Errata, only one of these is intended. As always, it's anyone's guess when or if this issue will be corrected in a future silicon rev. That it is likely to be fixed on any part that gets a die rev is not in doubt; when (and indeed whether) they will rev the die to fix all the nasty bugs that they've now corrected in their peripheral designs. (It would appear that Microchip only became aware of the issue after the Dx-series parts were released; it's the kind of thing where you could believe that it was intended if annoying behavior, which is probably why it wasn't noticed sooner (It came at the same time as the "TCA does what the datasheet says on RESTART command" (paraphrased, ofc), another bug like that; both may indeed have been intended at the time, and only later classified as bugs, perhaps by a newly installed product manager or QA czar who thought the original intent was folly (I do suspect that there was a changing of the guard around that time. That and/or an influx of testing manpower and resources. How else do you introduce a new ADC while reducing the number of errata from the previous generation from around 20, several serious, to like 5, of which this is the only one without a workaround); I happen to agree on this count and think it's a big deal (the other feature that got this treatment I agree with as well, but why did they ever let the timers have a port direction override? . Since there is no indication that there are die revs coming out any time soon, users are advised to proceed with use of workarounds, rather than delay work in the hopes of corrected silicon. + +The intended enable-protection is that a given logic block cannot be reconfigured while enabled. *This is handled by `init()` - you can write your new setting to a logic block, call `LogicN.init()` and it will briefly disable the logic block, make the changes, and re-enable it.* + +The unintended layer is that no logic block can be enabled or disabled (such as to be reconfigured) without also **disabling the whole CCL system** instead of just the one LUT. -The unintended layer is that no logic block can be reconfigured without also disabling the whole CCL system. Changes can be freely made to the `Logic` classes, however, only the `init()` method will apply those changes, and you must call `Logic::stop()` before calling `init()`, and `Logic::start()` afterwards. If/when parts become available where this is not necessary, this step may be omitted, and this library may be amended to provide a way to check. +Changes can always be freely made to the `Logic` classes - changes aren't written to the hardware until you call `init()`, so that's the only thing the CCL must be disabled for. On parts which are impacted by this (All tinyAVRs, the mega 0s and the DA and DB), you must call `Logic::stop()` before calling `init()`, and `Logic::start()` afterwards. On other parts, and the above listed parts if/when they get a silicon rev, you need not call `Logic::stop()/start()` - init() handles the per-LUT disable/enable correctly. +### Testing if the enable-lock erratum is present +At present, there is never a need to test this, because you know from the part family whether or not it has this erratum - nothing that shipped with this broken has gotten a die rev that fixed it, so it impacts all tinyAVR, mega0, DA, and DB. However DxCore provides #defines for all Arduino-relevant errata, and this errata can be tested like this; note that **this is not a macro and cannot be made a macro. The die rev is not compile time known!** How could it be? The compiler doesn't know what you're going to do with the hex file. +```c +if (checkErrata(ERRATA_CCL_PROTECTION)) { /*true if errata presnt */ + Logic::stop(); + Logic1.init(); + Logic::start(); +} else { // No erratum, hence no problem + Logic1.init(); +} +``` ### Example ```c++ // Imagine there's some code above this that configured and enabled Logic0. Logic1.truth=0x55; // new truth table -Logic1.input2=tca0; // and different input 2 +Logic1.input2=logic::in::tca0; // and different input 2 Logic3.enabled=true; // enable another LUT -Logic3.input0=in::link; // Use link from LUT0 -Logic3.input1=in::ac; // and the analog comparator -Logic3.input2=in::pin; // and the LUT3 IN2 pin +Logic3.input0=logic::in::link; // Use link from LUT0 +Logic3.input1=logic::in::ac; // and the analog comparator +Logic3.input2=logic::in::pin; // and the LUT3 IN2 pin Logic3.truth=0x79; // truth table for LUT3 Logic3.attachInterrupt(RISING,interruptFunction); // Interrupt now attached - but - Logic3 not enabled, and logic1 is using old settings +// If we don't care that Logic0 will be briefly delayed, the cautious approach is fully portable. Logic::stop(); // have to turn off Logic0 too, even though I might not want to Logic1.init(); // apply changes to logic block 1 Logic3.init(); // apply settings to logic block 3 for the first time Logic::start(); // re-enable +``` +Or, to "do the best it can" with the hardware, but use the more compact implementations without the runtime test if it's using parts the either always or never will have the bug: +```c++ +// Imagine there's some code above this that configured and enabled Logic0. +/* So the past is approximately: + * ConfigureLogic0(); + * ConfigureLogic1(); + * Logic0.init(); + * Logic1.init(); + * Logic::start(); + * And now you want to enable Logic3 and change Logic1 settings, if possible without disturbing Logic0. + */ + +void someFunction() { + Logic1.truth=0x55; // new truth table + Logic1.input2=logic::in::tca0; // and different input 2 + Logic3.enabled=true; // enable another LUT + Logic3.input0=logic::in::link; // Use link from LUT0 + Logic3.input1=logic::in::ac; // and the analog comparator + Logic3.input2=logic::in::pin; // and the LUT3 IN2 pin + Logic3.truth=0x79; // truth table for LUT3 + + Logic3.attachInterrupt(RISING,interruptFunction); + + // Interrupt now attached - but - Logic3 not enabled, and logic1 is using old settings + + applyCCLChanges(); + // you'll probably want to call this more than once - maybe with an argumnent that specifies which blocks to reinit, + // depending on your application, other logic might be able to be aggregated there to save flash. + +void applyCCLChanges() { + // Adjust to suit the logic blocks you need + // As noted at the start we wanted to avoid disturbing the Logic0 channel if possible, so we want to avoid the stop-start. + #if defined(ERRATA_CCL_PROTECTION) && ERRATA_CCL_PROTECTION == 1 + /* Some parts have not received a die rev and hence all extant specimens exhibit this erratum */ + Logic::stop(); // have to turn off Logic0 too, even though I might not want to + Logic1.init(); // apply changes to logic block 1 + Logic3.init(); // apply settings to logic block 3 for the first time + Logic::start(); // re-enable + #elif defined(ERRATA_CCL_PROTECTION) && ERRATA_CCL_PROTECTION == 0 + /* No version of the part has ever had this erratum, so there is no chance of encountering it */ + Logic1.init(); // apply changes to logic block 1 + Logic3.init(); // apply settings to logic block 3 for the first time + #else + /* Awesome! We got a die rev finally. Wait, crap, now I have to support both? */ + if checkErrata(ERRATA_CCL_PROTECTION) { + Logic::stop(); // have to turn off Logic0 too, even though I might not want to + Logic1.init(); // apply changes to logic block 1 + Logic3.init(); // apply settings to logic block 3 for the first time + Logic::start(); // re-enable + } else { + Logic1.init(); // apply changes to logic block 1 + Logic3.init(); + } + #endif +} ``` ## Think outside the box -To consider the CCL system as simply a built-in multifunction gate IC is to greatly undersell it. The true power of the CCL is in it's ability to use events directly, and to take inputs from almost everything. Even doing neat stuff like the above 0xD4 truth table on an even-numbered logic block with input 2 set to feedback to make an R/S latch without using the second logic block is only scratching the surface of what these can do! Taking that a step farther... you could then use the odd-numbered logic block with that same feedback to, say, switch between two waveforms being output by one of the PWM timers... see the [Tricks and Tips page](Tricks_and_Tips.md) +To consider the CCL system as simply a built-in multifunction gate IC is to greatly undersell it. The true power of the CCL is in it's ability to use events directly, and to take inputs from almost everything. Even doing neat stuff like the above mentioned "latch with no sequencer" is only scratching the surface of what these can do! Taking that a step farther... you could then use the odd-numbered logic block with that same feedback to, say, switch between two waveforms being output by one of the PWM timers, depending on what the latch is set to. See the [Tricks and Tips page](Tricks_and_Tips.md) ## Note on terminology Yes, technically, C++ doesn't have "properties" or "methods" - these are "member variables" and "member functions" in C++ parlance. They mean the same thing. I've chosen to use the more familiar, preseent day terminology. diff --git a/megaavr/libraries/Logic/Tricks_and_Tips.md b/megaavr/libraries/Logic/Tricks_and_Tips.md index d70481e4..2f03bf41 100644 --- a/megaavr/libraries/Logic/Tricks_and_Tips.md +++ b/megaavr/libraries/Logic/Tricks_and_Tips.md @@ -6,52 +6,123 @@ In the below examples X, Y, Z and 2 are used to refer to inputs. X, Y, and Z can Input 2, since it can be used as a clock, may be specifically required. -## Oscillation -You can make a CCL oscillate (and this is used in prescaling of clocks, see the bottom section). +## Feedback +The "Feedback" channel is the **output of the sequencer if used, and the even LUT in that pair if not**, + +### But I need feedback on an ODD LUT +"Ya can't get there from here". Well, you can, but it'll cost you. The price is one event channel: Use the output as a generator, and set one of the event inputs of the CCL block to that channel + +## A bit more on clocks and timing +The clocks have some counterintuitive behavior. First off, what do they and do they not effect? The clocks are used by: +* The Filter or Synchronizer, if enabled. +* The sequencer, if it is configured as a flip-flop. +* The Edge Detector +It does not govern the speed of the response otherwise - the CCL reacts asynchronously (it is not clocked, and happens as fast as the silicon can manage) unless the edge detector, synchronizer, filter, or sequencer are enabled. + +### The edge detector +Sometimes you need a pulse when all you have is a level. This gets you there. The clock is involved because the resulting pulse is 1 CCL clock long (occasionally this is not long enough, since the CCL clock can be faster than the system clock, particularly on the EB, where you can clock the CCL from the PLL, or you may be using a very slow clock, and it could be troublesome how long it is. + +### The ~programmable delay~ synchronizer/filter +This is one of the really cool, repurposable features. The intended use is that you can use the synchronizer to take a 2 clock cycle delay to ensure clean transitions and prevent glitches, with the filter meant to provide a means of cleanly handling more substantial noise by requiring that the signal be unchanged for 4 CCL clocks before outputting it. One nuisance is that according to the datasheet, one of these settings must be enabled to use the synchronizer. + +### The sequencer +The sequencer, in some modes, also uses the clock. It takes the clock from the EVEN LUT when acting as a flip-flop (but not when acting as a latch). + +### Ideas for application +Imagine you wanted a slightly longer pulse from a level change - say, 2 or 4 clocks clocks - than edge detector can get you. So you take a pair of LUTs, and enable the sequencer, with an R/S latch selected. Configure both of them identically. The one on the S line gets the synchronizer, and the one on the R line gets the filter. Result? When the inputs match the truth table, the signal will make it's way through the 2 stages of the synchronizer on both LUTs. This will set the sequencer output. Two clocks after that, the signal will make it through the second LUT and reset the sequencer. + +Or you can skip the sequencer, run the higher numbered LUT in level mode to interpret the inputs, and the adjacent one to make a pulse. You'd get 2 or 4 clock long pulse from it - but **that clock can be a different clock from the first one**. -X: Feedback +### The signal proceeds through a logic block's subcomponents in an order +It's important to understand the signal path. +1. The LUT - asynchronously monitors it's inputs and outputs a value based on the truth table. +2. The Filter/Synchronizer - Takes input from the LUT. Either does nothing, performs a 2 clock synchronization, or performs synchronization, then feeds that through 2 additional flipflops, and compares the output of the last one with the sync output, and only changes if the two match, indicating a signal stable for more than 2 clocks. This introduces a delay of 2 or 4 clocks +3. The edgedetector, if enabled, takes the output from the filter/synchronizer, and reacts to edges in that (note that per datasheet, you need to have one of those two enabled - unclear if that's "Or else" or "Because we put an interlock to prevent you from doing that"). That pulse starts as soon as the signal gets in, and lasts one clock. +4. Finally, if the sequencer is enabled, it overrides the output of the even LUT. +5. This signal - the sequencer if enabled / Even LUT's output if not - is what feeds both the pin (if enabled) and the signal that is provided as feedback (if used). + +Thus - the LUT comes first. It's output then may or may not be filtered/synchronized and if it is, may be edge-detected. If we imagine a LUT taking say, the OR of 2 inputs, and put a filter on it, and each of those inputs is (for simplicity) changing according to the same clock as the LUT. If the two inputs are, repeating: HLL and LHL, the output from the first stage will be HHL. The filter will then turn that into *a continuous high level*. + +## PWM is magic +Unlike the event channels, where you get a single clock long pulse from a compare match or overflow, the inputs to logic blocks are the level of the output compare! That means that you can remap pins that don't exist on your part but would have PWM if they did to the LUT output pin. + +### Long-armed PWM +Reach out and use a timer on a distant pin instead of the (otherwise occupied or non-existent) one it would normally get. + +INSEL: +```text +X: TCA WO0, TCB0 or TCD WOA Y: masked Z: masked +or +X: masked +Y: TCA WO1, TCB1 or TCD WOB +Z: masked +or +X: masked +Y: masked +Z: TCA WO2, TCB2 or TCD WOC (which is WOA) +``` + +LUT - Only one input is used, so there will be 2 bits that matter and 6 that don't (and should be left 0 by convention), one will be 0 (the low bit - you want ) and the other 1. So for non-inverted PWM, this is just +* 0x02 (0b00000010) +* 0x04 (0b00000100) +* 0x10 (0b00010000) + Clock: N/A -LUT: -000: 1 -001: 0 -TRUTH = 0x01 +Sync/Filter: Off + +Note that TCA WO3-5, TCB3+, and TCD WOD are not available as inputs. + +### Out-of-phase PWM +Problem: You have 3 output channels, each of which requires PWM at a different duty cycle (total not exceeding 100%), but they control large loads, and your power supply can only power one at a time. + +If you use a TCA (remembering to call the takeover function), you can run it in in SINGLE mode, getting 16 bits of resolution. + +If you then used it normally, the three duty cycles would be written to CMP0, CMP1, and CMP2, but they would all turn on at once, that's not okay. Instead, you could set them as: +* CMP0 = DS0 - 1 -> Output on the pin by setting pin output and writing 1 to the CMP0 bit +* CMP1 = DS0 + DS1 -1 -> Output through the CCL +* CMP2 = PER - DS3 -> Output on the pin by setting pin output, setting INVEN (inverting the pin), writing 1 to the CMP2 bit. -With no clock, this will oscillate at around 80-100 MHz and is highly sensitive to conditions. +CMP0 < CMP1 - Ensured by above, provided that: -It's much more useful if you put a clock source in, typically the system clock - See below for how to use this as a clock prescaler. +DS0 + DS1 <= PER - the duty cycles of the first two pins must not exceed 100% -## Switch -X: Input A -Y: Input B -Z: Select -Clock: as dictated by application +To meet the specific specification (one at a time only) we would need to be sure that DS0 + DS1 + DS2 <= PER, but not all applications require that of the final duty cycle (maybe there are only two big loads, and one light load... (possibly a cooling fan on the power supply?) + +INSEL: +* X: WO0 +* Y: WO1 +* Z: masked LUT: -000: 0 -001: 1 -010: 0 -011: 1 -100: 0 -101: 0 -110: 1 -111: 1 -TRUTH = 0xCB -Will output whatever input 0 is if input 2 is low, and whatever input 1 is if input 2 is high. +* 000: 0 - During the time before CMP0 is reached, the second big load shouldn't be on. +* 001: 1 - Now CMP0 has been reached but CMP1 hasn't, so here is where we want the to turn this output, controlling the second load. +* 010: 0 - This is never reachable in practice if CMP1 > CMP0. +* 011: 0 - Once CMP1 and CMP0 have been passed, we turn off WO -## PWM is magic -Unlike the event channels, where you get a single clock long pulse from a compare match or overflow, the inputs to logic blocks are the level of the output compare! That means that you can remap pins that don't exist on your part but would have PWM if they did to the LUT output pin. +Clock: N/A -Combine with count on event to switch between two inputs based on the value of the counter, +Sync/Filter: Off +Yes, we could have done the third one with another CCL lut too - but why when there's a trick to do it without wasting a second LUT? Maybe we need the other LUTs. -## Feedback -The "Feedback" channel is the **output of the sequencer if used, and the even LUT in that pair if not**, +### Modulated PWM +Like classic AVRs had on larger pincount devices. One PWM frequency should be significantly higher than the other if you're trying to modulate it, rather than measure the beat frequency or something, and they definitley should be at different frequencies, otherwise see the previous pattern. -### But I need feedback on an ODD LUT -"Ya can't get there from here" The only way to do this is he price in the form of one event channel: Use the output as a generator, and set one of the event inputs of the CCL block to that channel +INSEL: +* X: Timer PWM channel. +* Y: Second PWM channel. +* Z: masked + +LUT: 0x08 (0b00001000, HIGH if both inputs are high) + +Clock: N/A + +Sync/Filter: Off + +### ## Sequential logic with just one LUT You can simulate some sequential logic units with just one LUT! @@ -59,10 +130,11 @@ You can simulate some sequential logic units with just one LUT! Enable the synchronizer to get the analogous flip-flop. ### S-R latch -X: Feedback -Y: Set (any input source) -Z: Clear (any input source) -Clock: N/A + +INSEL: +* X: Feedback +* Y: Set (any input source) +* Z: Clear (any input source) LUT: * 000: 0 @@ -73,13 +145,18 @@ LUT: * 101: 0 * 110: Per application requirements - logic block is getting contradictroy signals * 111: Per application requirements - logic block is getting contradictroy signals -Ergo: TRUTH = 0x0b??001110 = 0x07, 0xC7. Using 0x47 or 0x87 will result in high speed oscillation, which is rarely desirable. +Ergo: TRUTH = 0x0b??001110 = 0x07 (go low when told to go both directions), 0xC7 (go high when...) or 0x47 (don't change when...). Avoid 0x87 (Oscillate rapidly at an unpredictable speed when...) + +Clock: N/A for latch, anything except IN2 as clock as demanded by application for flipflop. + +Sync/Filter: Off for latch, on for flip-flop. ### D-Type latch -X: Feedback -Y: D (gated signal to latch) -Z: G (Gate) -Clock: N/A + +INSEL: +* X: Feedback +* Y: D (gated signal to latch) +* Z: G (Gate) LUT: * 000: 0 @@ -92,6 +169,72 @@ LUT: * 111: 1 Ergo: TRUTH = 0xCB +Clock: N/A for latch, anything except IN2 as clock as demanded by application for flipflop. + +Sync/Filter: Off for latch, on for flip-flop. + +## More patterns + +### A/B select +Allows selection of one of two inputs as it's output based on the remaining input. Obviously, chainable if you have to. + +INSEL: +* X: Selector +* Y: A - output when selector low +* Z: B - output when selector high + +LUT: +* 000: 0 +* 001: 0 +* 010: 1 +* 011: 0 +* 100: 0 +* 101: 1 +* 110: 1 +* 111: 1 + +Ergo: TRUTH = 0xE4 + +### Gated Buffer +INSEL: +* X: D - When G is high, D is output +* Y: G - When G is low, the output is low. +* Z: masked + +LUT: +* 000: 0 +* 001: 0 +* 010: 0 +* 011: 1 +Ergo: TRUTH = 0x08 + +Clock: N/A + +Sync/Filter: Off + +### Double-gated Buffer +There are many variants on this where different combinations of logic are used. The point is to get an "If A and B, output C, else output (whatever level it is" + +INSEL: +* X: D - When G is high, D is output +* Y: G - When G1 is low, the output is low. +* Z: /G - When G is high, the output is low. + +LUT: +* 000: 0 +* 001: 0 +* 010: 0 +* 011: 1 +* 100: 0 +* 101: 0 +* 110: 0 +* 111: 0 +Ergo: TRUTH = 0x08 + +Clock: N/A + +Sync/Filter: Off + ## Prescaling clocks with the CCL You can use CCL logic blocks to prescale a clock, albeit inefficiently. On parts with 6 LUTs, you can prescale by 2^18 if you're willing to use all LUTs and 3 event channels. This can be used for example to clock a TCB from a prescaled value that is slower than half the system clock but not used by any TCA. diff --git a/megaavr/libraries/Logic/examples/LatchNoSeq/LatchNoSeq.ino b/megaavr/libraries/Logic/examples/LatchNoSeq/LatchNoSeq.ino index 35b8eafe..db839cc0 100644 --- a/megaavr/libraries/Logic/examples/LatchNoSeq/LatchNoSeq.ino +++ b/megaavr/libraries/Logic/examples/LatchNoSeq/LatchNoSeq.ino @@ -1,15 +1,36 @@ /***********************************************************************| | Configurable Custom Logic library | | Developed in 2019 by MCUdude. https://github.com/MCUdude/ | +| Ported to DxC and maintained for mTC and DxC by Sprence Konde | | Example by Spence Konde | | | | LatchNoSeq.ino - Getting "RS Latch" like behavior with a single even | -| numbered LUT. | +| numbered LUT - or with an odd LUT and an event channel | | | | In this example we use the configurable logic peripherals in AVR | | to act as a "latch" WITHOUT using both LUTs and the sequencer | | For the even-numbered logic block(s) we can simply use the feedback | | input. Otherwise we need to use the event system. | +| Because we can use the output of the LUT, what we want is a truth | +| table that will maintain a HIGH or LOW if no buttons are prssed. We | +| also need to make sure that it handles the "both buttons pressed" | +| case gracefully; three of the 4 options work: +| * Maintain current state if both buttons are depressed. This means | +| while both buttons are pressed, the output will be as dictated by +| the first button pressed, and when one button is released, if the | +| other button would flip it, it will. This was used here, partly | +| because the truth table ends up the same whether the inputs are | +| active high or low | +| * Set the output LOW whenever both inputs are asserted. | +| Assuming active low inputs, this corresponds to truth = 0x8C | +| * Set the output HIGH whenever both inputs are asserted | +| Assuming active low inputs, this corresponds to truth = 0x8F | +| * ~Set the output to the opposite state when both inputs asserted~ | +| This does not produce something that acts like a latch. It will | +| oscillate at a very high frequency when both inputs are on, as it | +| will be switching between two state, each of which tell it to | +| switch again. This would correspond to 0x8D in the truth table; so | +| avoid that one. | 3-input truth table: | | We use CCL LUT event as our "feedback", |PA2|PA1|CCL| Y | | | PA1 is RESET and PA2 is SET, both |---|---|---|---| | @@ -24,8 +45,13 @@ | | | The sky (well, and the number of LUTs) is the limit!! | | | -| Warning: This involves EVSYS, which is very different on tiny0/1 vs | -| everything else. Hence, many #ifdefs. | +| Warning: | +| The same library is used for mTC and DxC; this example is used on both| +| As it is used in our compile testing, it needs to compile on both the | +| tinyAVRs and the Dx and Ex series. As a consequence, because this | +| involves EVSYS, which is very different on tiny0/1 vs everything else | +| a number of #ifdef statements are used to select the right | +| implementation. | |***********************************************************************/ #include @@ -33,14 +59,14 @@ void setup() { // Initialize logic block 0 // Logic block 0 has three inputs, PA0, PA1 and PA2. - // These are the pins directly above the UPDI pin - // Because PA0 is shared with the UPDI pin and is not usually an option - // we use PA3 via the event system in this example on ATtiny parts - // It has one output, PA5 on ATtiny, or alternate PB6 on 20 and 24-pin ATtiny. + // Because PA0 is shared with the UPDI pin on tinyAVR parts + // we use the other two as our button inputs. + // It outputs on the LUT0 OUT pin - PA4 (alt. PB4) on ATtiny + // or PA2 (alt PA6) everywhere else. Logic0.enable = true; // Enable logic block 0 - #ifdef EVSYS_CHANNEL0 // means it's a 2-series, where the event system works like it does on everything other than the tinyAVR 0/1-series + #if defined(MEGATINYCORE) || defined(MEGATINYCORE_SERIES) != 2 EVSYS.CHANNEL0 = EVSYS_CHANNEL0_CCL_LUT0_gc; EVSYS.USERCCLLUT1A = EVSYS_USER_CHANNEL0_gc; #else // it's a tinyAVR 0/1 @@ -63,25 +89,20 @@ void setup() { #ifdef EVSYS_CHANNEL0 // means it's not a 0/1-series EVSYS.CHANNEL0 = EVSYS_CHANNEL0_CCL_LUT1_gc; EVSYS.USERCCLLUT1A = EVSYS_USER_CHANNEL0_gc; - #else // it's a tinyAVR 0/1 + #else // it's a tinyAVR 0/1 - EVSYS is weird there EVSYS.ASYNCCH0 = EVSYS_ASYNCCH0_CCL_LUT1_gc; // Use CCL LUT1 as event generator EVSYS.ASYNCUSER4 = EVSYS_ASYNCUSER2_ASYNCCH0_gc; // ASYNCUSER4 is LUT1 event 0 #endif - Logic1.input0 = logic::in::event_a; // same distribution of inputs (though there isn't the tinyAVR PA0 issue forcing it here) - Logic1.input1 = logic::in::input_pullup; // get it from LUT 1 event a or event 0 (tinyAVR 0/1 documentation calls them 0 and 1 - Logic.h accepts both for all parts) - Logic1.input2 = logic::in::input_pullup; - // Logic0.output_swap = logic::out::pin_swap; // Uncomment this line to route the output to alternate location, if available. - Logic1.output = logic::out::enable; // Enable logic block 1 output pin (see pinout chart) - Logic1.filter = logic::filter::disable; // No output filter enabled - Logic1.truth = 0x8E; // Set truth table - HIGH only if both high - - // Initialize logic block 0 - Logic0.init(); - - - // Start the AVR logic hardware - Logic::start(); + Logic1.input0 = in::event_a; // same distribution of inputs (though there isn't the tinyAVR PA0 issue forcing it here) + Logic1.input1 = in::input_pullup; // | get it from LUT 1 event a or event 0 (tinyAVR 0/1 documentation calls them 0 and 1 + Logic1.input2 = in::input_pullup; // | Logic.h accepts both for all parts, but we recommend the new nomenclature) + //Logic1.output_swap = out::pin_swap; // Uncomment this line and comment out the next one to route the output to alternate location, + Logic1.output = out::enable; // | if available. Output pins on Dx are pins 3 and 6 on the LUT's "home port". + Logic1.filter = filter::disable; // No output filter enabled + Logic1.truth = 0x8E; // Set truth table - 0 = LOW, 1-3 = HIGH, 4-6 = LOW, 7 = HIGH + Logic1.init(); // Initialize logic block 0 + Logic::start(); // Start the AVR logic hardware } void loop() { diff --git a/megaavr/libraries/Logic/library.properties b/megaavr/libraries/Logic/library.properties index badcbf43..5c58dfce 100644 --- a/megaavr/libraries/Logic/library.properties +++ b/megaavr/libraries/Logic/library.properties @@ -1,9 +1,9 @@ name=Logic -version=1.3.0 +version=1.3.1 author=MCUdude and Spence Konde maintainer=MCUdude and Spence Konde sentence=A library for interfacing with the customizable logic in megaAVR 0-series, tinyAVR 0/1/2-series, and Dx-series chips. -paragraph=1.3.0 add enclosing namespace to fix conflicts with other @MCUdude libraries. 1.2.x correct tinyAVR bugs. 1.1.3 - Correct bugfor tinyAVR - cleanup and harmonize with tinyAVR copy. This is the megaTinyCore version of documentation and examples. The code is identical. +paragraph=1.3.1 = Minor Maintenance. 1.3.0 added enclosing namespace to fix conflicts with other @MCUdude libraries. 1.2.x correct tinyAVR bugs. 1.1.3 - Correct bugfor tinyAVR - cleanup and harmonize with tinyAVR copy. This is the megaTinyCore version of documentation and examples. The code is identical. category=Signal Input/Output url=https://github.com/SpenceKonde/megaTinyCore dot_a_linkage=true diff --git a/megaavr/libraries/Logic/src/LogicEnums.h b/megaavr/libraries/Logic/src/LogicEnums.h index b86ad7aa..ae95f659 100644 --- a/megaavr/libraries/Logic/src/LogicEnums.h +++ b/megaavr/libraries/Logic/src/LogicEnums.h @@ -75,6 +75,7 @@ namespace logic { tca1 = 0x0B, tcb = 0x0C, tcd = 0x0D, + tcd0 = 0x0D, input_pullup = 0x15, input = 0x25, input_no_pullup = 0x25, @@ -104,6 +105,7 @@ namespace logic { tca = 0x0A, tcb = 0x0C, tcd = 0x0D, + tcd0 = 0x0D, input_pullup = 0x15, input = 0x25, input_no_pullup = 0x25, @@ -134,7 +136,29 @@ namespace logic { input_pullup = 0x15, input = 0x25, input_no_pullup = 0x25, - + #elif defined(__AVR_AVR8EB28__) || defined(__AVR_AVR8EB32__) || \ + defined(__AVR_AVR16EB32__) || defined(__AVR_AVR32EA32__) || \ + defined(__AVR_AVR16EB20__) || defined(__AVR_AVR16EB32__) || \ + defined(__AVR_AVR16EB28__) || defined(__AVR_AVR16EB32__) || \ + defined(__AVR_AVR32EB20__) || defined(__AVR_AVR32EB14__) || \ + defined(__AVR_AVR32EB28__) || defined(__AVR_AVR32EA32__) + masked = 0x00, + unused = 0x00, + disable = 0x00, + feedback = 0x01, + link = 0x02, + event_0 = 0x03, + event_a = 0x03, + event_1 = 0x04, + event_b = 0x04, + pin = 0x05, + ac = 0x06, + usart = 0x08, + spi = 0x09, + tcb = 0x0C, + input_pullup = 0x15, + input = 0x25, + input_no_pullup = 0x25, #else //tinyAVR 0/1-series masked = 0x00, unused = 0x00, @@ -153,6 +177,7 @@ namespace logic { tca = 0x08, tca0 = 0x08, tcd0 = 0x09, + tcd = 0x09, usart = 0x0A, usart0 = 0x0A, spi = 0x0B, diff --git a/megaavr/libraries/SD/library.properties b/megaavr/libraries/SD/library.properties index b9c2faef..bb98d8c5 100644 --- a/megaavr/libraries/SD/library.properties +++ b/megaavr/libraries/SD/library.properties @@ -1,9 +1,9 @@ name=SD version=1.2.4 author=Arduino, SparkFun -maintainer=Arduino -sentence=Enables reading and writing on SD cards. -paragraph=Once an SD memory card is connected to the SPI interface of the Arduino or Genuino board you can create files and read/write on them. You can also move through directories on the SD card. +maintainer=Spence Konde +sentence=Enables reading and writing on SD cards; Adapted to ensure that it works on all modern AVRs, instead of just the m4809. Otherwise identical to the stock version. +paragraph=Once an SD memory card is connected to the SPI interface you can create files and read/write on them. You can also move through directories on the SD card.
This version contains trivial adaptations to ensure functionality on all post-2016 "modern AVRs", while the official version only works with the mega4809. No other changes are made, and the relevant change is trivial. category=Data Storage url=https://www.arduino.cc/reference/en/libraries/sd/ architectures=megaavr diff --git a/megaavr/libraries/Servo/examples/Knob/Knob.ino b/megaavr/libraries/Servo/examples/Knob/Knob.ino index ad35c3f7..ec7237ec 100644 --- a/megaavr/libraries/Servo/examples/Knob/Knob.ino +++ b/megaavr/libraries/Servo/examples/Knob/Knob.ino @@ -15,7 +15,7 @@ int potpin = PIN_PA7; // analog pin used to connect the potentiometer - analog int val; // variable to read the value from the analog pin void setup() { - myservo.attach(9); // attaches the servo on pin 9 to the servo object + myservo.attach(PIN_PA1); // attaches the servo on PA1, this pin exists on all tinyAVRs. } void loop() { diff --git a/megaavr/libraries/Servo/src/megaavr/ServoTimers.h b/megaavr/libraries/Servo/src/megaavr/ServoTimers.h index c81f467d..0a03a445 100644 --- a/megaavr/libraries/Servo/src/megaavr/ServoTimers.h +++ b/megaavr/libraries/Servo/src/megaavr/ServoTimers.h @@ -53,7 +53,7 @@ #else #define USE_TIMERB2 #endif - // ah, finally cases that might be relevant to tinyAVR + #elif (defined(TCB1) && defined(SERVO_USE_TIMERB1)) #if defined(MILLIS_USE_TIMERB1) #error "SERVO_USE_TIMERB1 is defined, but so is MILLIS_USE_TIMERB1 - TCB1 can only be used for one of these." @@ -66,7 +66,7 @@ #else #define USE_TIMERB0 #endif - // No defines try to force a specific timer, use TCB1 if it exists unless it's being used for millis. + // No defines try to force using a specific timer, use TCB1 if it exists unless it's being used for millis. #elif (defined(TCB1) && !defined(MILLIS_USE_TIMERB1)) #define USE_TIMERB1 #elif !defined(MILLIS_USE_TIMERB0) diff --git a/megaavr/libraries/Wire/README.md b/megaavr/libraries/Wire/README.md index 7a06606d..e1acf4f2 100644 --- a/megaavr/libraries/Wire/README.md +++ b/megaavr/libraries/Wire/README.md @@ -26,7 +26,7 @@ Given as SDA, SCL - notice that the pins within PORTB aren't numbered in the sam Notes: * Only tinyAVR has the backwards port weirdness. Dx and Ex have all ports in the same order, and SDA is always 1 pin before SCL. * Alt3 is only available on AVR DD-series, EA-series and likely future Dx and Ex parts). -* Alt1 is not available on parts which do not have PC6 and PC7 (for TWI0) or PB6, PB7 (for TWI1) because it would be identical to the default mapping. +* Alt1 on Dx/Ex parts is not available unless they have PC6 and PC7 (for TWI0) or PB6, PB7 (for TWI1) because it would be identical to the default mapping. * Alt2 is available on those parts, since its's primary pins are different - though dual mode is not available if the pins aren't present. * If a part does not have the listed SCL and SDA pins, that mapping is not available on those parts. * In all cases the pins are listed as SDA, SCL. @@ -36,11 +36,11 @@ Availability of pin mappings by pincount for AVR Dx-series |----------|----------------|--------|--------|--------|--------|--------|--------| | PA0, PA1 | M/S only | No `**`| For EA | For DD | For DD | Yes | Yes | | PA2, PA3 | M/S only | Yes | Yes | Yes | Yes | Yes | No | -| PC2, PC3 | Either | Yes | Yes | Yes | Yes | Yes | Yes | +| PC2, PC3 | Either | Yes | Yes | Yes `?`| Yes `?`| Yes `?`| Yes `?`| | PC6, PC7 | Dualmode Slave | Yes | Yes | No | No | No | No | -| PF2, PF3 | M/S only | Yes | Yes | Yes | No | No | No | -| PB2, PB3 | Either | Yes | Yes | No | No | No | No | -| PB6, PB7 | Dualmode Slave | Yes | No | No | No | No | No | +| PF2, PF3 | M/S only `*` | Yes | Yes | Yes | No | No | No | +| PB2, PB3 | Either `*` | Yes | Yes | No | No | No | No | +| PB6, PB7 | Dualmode Slave `*`| Yes | No | No | No | No | No | `*` - This is a Wire1 pin option, and is only available where there are two TWI interfaces (currently, the DA and DB series). @@ -50,13 +50,14 @@ Availability of pin mappings by pincount for AVR Dx-series `Wire.pins(SDA pin, SCL pin)` - this will set the mapping to whichever mapping has the specified pins `SDA` and `SCL`. See API reference below for details. Wire.pins() only supports specifying mapping by pins for the master/slave pins, not the dualmode slave only pins. If you want the mode with the pins that can't do dual mode slave (PA2/3, or PF2/3) but with the alternate slave pins, you MUST use Wire.swap(). +In all cases, the part specific documentation included with the core takes precedence over this document and will include information about parts not mentioned here. ## Official specification of I2C -~From NXP, the current owner of the relevant IP~ -Gee, that link didn't last long did it.... +[From NXP, the current owner of the relevant IP](https://www.nxp.com/docs/en/user-guide/UM10204.pdf) +Grab it while you can! They'll probably pull it down again. ## Overview - I2C, what is it? I2C (known by many names, see note at end) uses two pins, a clock (SCL) and data (SDA) for communication among two or more compatible devices. This is an open drain bus - external pullup resistors (*which you must include in your design*) keep the two lines HIGH when idle, and devices communicate by driving the pins low or releasing them. Data is clocked on the rising edge (this is a more important detail than usual, as you will see). -In each transaction, one device, the "master" or "host" initiates communication by writing a "start condition" followed by clocking out a an 1 byte address. The "slave" or "client" device with that address will send a single bit in response (the ACK bit). When using this library to make a slave device, this is configurable. Otherwise, it may be fixed, software-configurable, set by an address pin state, etc; Refer to the applicable datasheet.) The lowest bit of the address indicates whether the master is going to read or write. For a write, it will continue clocking out another byte when it gets the ACK bit, and this will repeat until either the slave refuses to ACK a byte (a NOACK) or the master is done sending. For a read, the after the address is ACK'ed, the master will continue to provide a clock, and allow the slave to control the data line. The master will ACK each byte until has read as many bytes as it is attempting to. +In each transaction, one device, the "master" or "host" initiates communication by writing a "start condition" followed by clocking out a an 1 byte address. The "slave" or "client" device with that address will send a single bit in response (the ACK bit). When using this library to make a slave device, this is configurable. Otherwise, it may be fixed, software-configurable, set by an address pin state, etc; Refer to the applicable datasheet.) The lowest bit of the address indicates whether the master is going to read or write. For a write, it will continue clocking out another byte when it gets the ACK bit, and this will repeat until either the slave refuses to ACK a byte (a NOACK) or the master is done sending. For a read, the after the address is ACK'ed, the master will continue to provide a clock, and allow the slave to control the data line. The master will ACK each byte until has read as many bytes as it is attempting to, responding to the last byte with a NACK. A start or stop condition is simply the data line being changed while the clock line is high; SDA H->L while SCL is high is a start, SDA L-> H is a stop. Otherwise, SDA is only asserted (driven low) or released when SCL is low. The "ACK" bit is generated by whichever device is receiving data, by driving SDA low after the 8th bit is received. A NACK is simply not doing so (so a device that "sends" a NACK, and a device that is not present at all, look the same to the master). @@ -79,9 +80,9 @@ If you know the bus capacitance, you can calculate the maximum value with that u None of that accounts for the fact that wires, particularly long ones, have non-zero inductance, and the impact of this on rise and fall times is harder to calculate. *I2C was designed to be used between ICs on a circuit board* and long wires will degrade it's performance, requiring stronger pullups and/or lower speeds than you would otherwise be able to use - note that while lowering the speed allows you to use weaker pullups or survive a higher bus capacitance, the minimum pullup value (hence strongest pullups) that can be used is fixed. Most people don't calculate the pullup values - we take an educated guess, and the window is wide enough that standard mode is rarely a problem. For small numbers of parts at standard speed, **4.7k is a good default value**, and *1.5-10k will generally be fine*. At higher frequencies, a smaller resistor might be required, see `Wire.setClock()` for the recommended values. When pulling up to a voltage lower than 5V, you typically need stronger pullups - but this is not usually a problem, since the minimum pullup value also falls. -The internal pullups, however, are typically in the area of 30-50k. That may be okay for 2 devices at standard speed. Even 3-4 devices gets dicey, and wiring could sink even the 2-device case. By default, most classic AVR cores, including the official ones, turn on the internal pullups - giving a default configuration that would work under simple conditions. But as more devices were added, the bus would fail unpredictably, and the failures are often difficult to pin down and intermittent (one would typically wind up debugging a system right on the edge of failing). We don't enable them by default. If you want to use the internal (insufficient) pullups instead of using external ones, go ahead, calling `Wire.usePullups()` after choosing the pins- but do so only with the knowledge that it only has a chance of working on small networks, and may be unreliable or unusable on larger one. *Wire.usePullups() is intended for debuggign only! If it fixes anything, check the external pullups, because one or both are absent or not connected properly* +The internal pullups, however, are typically in the area of 30-50k (per spec. Room temperature, they're pretty close to 30k most of the time). That may be okay for 2 devices at standard speed. Even 3-4 devices gets dicey, and wiring could sink even the 2-device case. By default, most classic AVR cores, including the official ones, turn on the internal pullups - giving a default configuration that would work under simple conditions. But as more devices were added, the bus would fail unpredictably, and the failures that result are resistant to analysis. We don't enable them by default, that means that there is a 100% chance of failure if no external pullups are included, allowing the existence of a problem to be detected and diagnosed. If you want to use the internal (insufficient) pullups instead of using external ones, go ahead, calling `Wire.usePullups()` after choosing the pins with swap() - but *do so only with the knowledge that it is out of spec* and *cannot scale to a network with more devices*. Because of this, **`Wire.usePullups()` is intended for debugging only!** If it fixes anything, the external pullups are either absent, not connected properly or at all (cold solder joints? head in pillow defects are surprisingly common in home reflowed boards). -The ease of using multiple voltages on an open drain bus was mentioned above, but it's worth elaborating a bit here. The standard certainly doesn't guarantee that a 5V device will recognize an I2C line only pulled up to 3.3V as high (though it generally will) - but on the AVR Dx and AVR Ex parts, there's an option to let you do far better than that: "SMBUS 3.0" voltage levels. This option also, by lowering the threshold voltages, can help cope with high bus capacitance (be careful of the case where devices running at over 3.3v are present which don't have this option enabled, especially if pushing to higher clock speeds. ) +The ease of using multiple voltages on an open drain bus was mentioned above, but it's worth elaborating a bit here. The standard certainly doesn't guarantee that a 5V device will recognize an I2C line only pulled up to 3.3V as high (though it generally will) - but on the AVR Dx and AVR Ex parts, there's an option to let you do far better than that: "SMBUS 3.0" voltage levels. This option also, by lowering the threshold voltages, can help cope with high bus capacitance (be careful of the case where devices running at over 3.3v are present which don't have this option enabled, especially if pushing to higher clock speeds, as they might not recognize the highs, or recognize them for long enough - This option is best used with a bus full of devices that use lower logic levels). ## Valid Addresses Addresses are 7 bits - 8 bits are sent, and the least significant one indicates if it's a read or write operation. This leaves 128 addresses, however, some of them are "reserved", and have a special semantic meaning in I2C and I2C-compatible protocols. @@ -126,38 +127,44 @@ There is a right and a wrong order to call the configuration functions. This ord See the API reference below for more information. -## API reference -This is a full listing of methods provided for the TwoWire class (the class is named TwoWire, and Wire is an object of class TwoWire). Where they exist and behave the same way as documented in the Arduino Wire API reference they are simply listed. Where they do not, it is described here. -### The TwoWire class -Wire is an object of class TwoWire. The classic AVR Wire.h, like this library, has TwoWire as a subclass of Stream. -The official megaAVR 0-series core that megaTinyCore was based on in the distant past subclassed a new "HardwareI2C" class. Unfortunately, that imposed a shocking amount of overhead with no practical benefit. Code that relies on TwoWire being a subclass of HardwareI2C is virtually non-existent, and code that would benefit from an 500 bytes or so of flash is very common. Any library you encounter that works on classic AVRs (e.g., Uno) but complains of this different inheritance is straightforward to fix, likely as simple as searching the library files for "HardwareI2C" and changing it to "Stream". ### New Tools submenu: Wire Mode -DA and DB devices have all of these options. Others only have the first two +All devices have at least 2 options, * Master or Slave (default) - This uses the least flash and ram. At any given time Wire can be a master or a slave, but not both and you must call Wire.end(), and then the appropriate form of begin() for the mode you want to enable. * Master And Slave - In this mode, an argumentless call to begin will start the master functionality, and a call to the form with one or more arguments will start the slave version. Both can run at the same time either using DualMode, or on the same pins (multi-master). -* Master or Slave x2 - In this mode, there is a Wire, and a Wire1 - corresponding to TWI0 and TWI1 peripherals. -* Master and Slave x2 - Combination of the two above options - Both Wire and Wire1 are provided, and *both* can be both a master and a slave at the same time, for a total of 4 pairs of I2C pins (note: having more than one I2C slave defined at once is not recommended, though this library should work. ) +* Master or Slave x2 - In this mode, there is a Wire, and a Wire1 - corresponding to TWI0 and TWI1 peripherals; available on DA and DB only. You can have two masters, two slaves, or most usefully, one of each) +* Master and Slave x2 - Both Wire and Wire1 are provided, and *both* can be both a master and a slave at the same time, for a total of 4 pairs of I2C pins (note: having more than one I2C slave enabled at once is not recommended, though this library should work) +## API reference +### The TwoWire class +Wire is an object of class TwoWire. The classic AVR Wire.h, like this library, has TwoWire as a subclass of Stream. +The official megaAVR 0-series core that megaTinyCore was based on in the distant past subclassed a new "HardwareI2C" class. Unfortunately, that imposed a shocking amount of overhead with no practical benefit. Code that relies on TwoWire being a subclass of HardwareI2C is virtually non-existent, and code that would benefit from an 500 bytes or so of flash is very common. Any library you encounter that works on classic AVRs (e.g., Uno) but complains of this different inheritance is straightforward to fix, likely as simple as searching the library files for "HardwareI2C" and changing it to "Stream". +Wire is an object of class TwoWire. The classic AVR Wire.h, like this library, has TwoWire as a subclass of Stream. +The official megaAVR 0-series core that megaTinyCore was based on in the distant past subclassed a new "HardwareI2C" class. Unfortunately, that imposed a shocking amount of overhead with no practical benefit (due to the whole virtual function thing). Code that relies on TwoWire being a subclass of HardwareI2C is virtually non-existent, and code that would benefit from the added flash is very common, especially considering the popularity of the ATtiny412, which is sadly the largest-flash 8-pin part. . Any library you encounter that works on classic AVRs (e.g., Uno) but complains of this different inheritance is straightforward to fix, likely as simple as searching the library files for "HardwareI2C" and changing it to "Stream". +This is a full listing of methods provided for the TwoWire class (the class is named TwoWire, and Wire is an object of class TwoWire). Where they exist and behave the same way as documented in the Arduino Wire API reference they are simply listed. Where they do not, it is described here. ### Methods not present in official Arduino Wire library This version adds several new methods to support additional functionality. +#### `bool swap()` ```c++ -bool swap(uint8_t state = 1); +bool swap(uint8_t pinset = 1); ``` -This will set the pin mapping to the selected pinset (see the table at top of this document). Only 1-series parts with more than 8 pins support this; on other parts, `Wire.swap()` will generate a compile error if a value known at compile time and not 0 is passed to it. On 1-series parts that do have an alternate pin mapping, a compile-time-known value that is not a 0 or 1 will similarly generate a compile error. An invalid value that is *not* known at compile time (in either case) will instead result in swap() returning false and selecting the default pins. +This will set the pin mapping to the selected pinset (see the table at top of this document). Only tinyAVR 1+Series (16k and 32k 1-series parts) support this. All other modern non-tinyAVRs do. On tinyAVR 0/2-series or on 8-pin tinyAVRs of either series, `Wire.swap()` will generate a compile error if a value known at compile time and not 0 is passed to it. On 1-series parts that do have an alternate pin mapping, a compile-time-known value that is not a 0 or 1 will similarly generate a compile error. An invalid value that is *not* known at compile time (in either case) will instead result in swap() returning false and selecting the default pins. +#### `bool pins()` ```c++ bool pins(uint8_t sda_pin, uint8_t scl_pin); ``` This will set the the pin mapping to the specified set of pins. If this is not a valid mapping option, it will return false and set the mapping to the default. This uses more flash than `Wire.swap()`; that method is preferred. As with `Wire.swap()`, this will generate a compile error if the pins are compile-time-known constants which are not a valid SDA/SCL pair, and pins not known at compile time will return false select the default pin mapping. +#### `uint8_t getIncomingAddress()` ```c++ uint8_t getIncomingAddress(); ``` This returns the last incoming address which most recently matched a slave's address, secondary address, or masked-address. Critical to using the general call alternate/masked address options. See the secondary and masked address examples. +#### `twi_buffer_index_t getBytesRead()` ```c++ twi_buffer_index_t getBytesRead(); ``` @@ -171,48 +178,56 @@ This method is only useful when operating as a slave. See the register model exa `twi_buffer_index_t` is a `uint8_t` unless the buffer length has been set to more than 255 bytes, in which case it is a `uint16_t` +#### `uint8_t slaveTransactionOpen()` ```c++ uint8_t slaveTransactionOpen(); ``` This method, when called by an I2C slave, will return a value indicating whether the slave is busy (that is, if it has received a read command matching its address, but has either not sent any data, or has sent some data, but has not yet received a NACK after transmitting a byte to the master (which would indicate that the master is done reading from it) - in other words, if it is in the process of sending requested data to the master). If you want to enter sleep mode or change which sleep mode is selected you must make sure this returns 0. As of 2.6.2 we have failsafe measures in place (see sleep section below) to handle this eithout risk failing to let go of the bus, see the sleep section below; it is still possible to not end up in sleep mode in this case from just one call to sleep_cpu(). +#### `void endMaster()` ```c++ -endMaster(); +void endMaster(); ``` This is analogous to `Wire.end()`, but only effects the master functionality, as the name implies. +#### `void endSlave()` ```c++ -endSlave(); +void endSlave(); ``` This is analogous to `Wire.end()`, but only effects the slave functionality, as the name implies. +#### `void usePullups()` ```c++ -usePullups(); +void usePullups(); ``` Unlike the official core, we do not automatically turn on the internal pullups, specifically because it can hide problems in simple tests - but not more complicated cases. Combined with the frustrating failure modes of I2C in general (not specific to this library) this can lead to a very challenging debugging experience if/when it does manifest as most I2C devices are added or longer wires are used, possibly dependent on orientation and spatial organization. Thus, we require that you read this paragraph and recognize that it could fail unpredictably before enabling the internal pullups. This is particularly problematic since Arduino users are accustomed to not having to think much about things like wire length and capacitance of wire; this is one of only a few cases where they often become relevant. +#### `uint8_t checkPinLevels()` ```c++ uint8_t checkPinLevels(); ``` This function returns the level of the master TWI pins, depending on the used TWI module and port multiplexer settings. Bit 0 represents SDA line and bit 1 represents SCL line. This is useful on initialisation, where you want to make sure that all devices have their pins ready in open-drain mode. A value of 0x03 indicates that both lines have a HIGH level and the bus is ready. -#### Additional New Methods not available on all parts +### Additional New Methods not available on all parts These new methods are available exclusively for parts with certain specialized hardware; Most full-size parts support enableDualMode (but tinyAVR does not), while only the DA and DB-series parts have the second TWI interface that swapModule requires. +#### `void swapModule()` ```c++ -swapModule(TWI_t *twi_module); +void swapModule(TWI_t *twi_module); ``` This function is only available if the hardware has two modules (DA or DB with 32+ pins); this allows you to swap the Wire object over to use TWI1, allowing the TWI1 pins to be used without creating both Wire and Wire1 - either because you need to use a library hardcoded to use Wire, not Wire1, or because you need to use the TWI0 pins. This must be called first, before `Wire.enableDualMode()` or `Wire.begin()`. Accepts `&TWI0` and `&TWI1` as arguments. This method is available ONLY if both TWI0 and TWI1 are present on the device, but the tools -> Wire mode menu is not set to an option that creates Wire1. The point is to provide a facility to, without the overhead of both Wire modules, use the TWI1 pins instead of the TWI0 pins. +#### `void enableDualMode()` ```c++ -enableDualMode(bool fmp_enable); // Moves the Slave to dedicated pins +void enableDualMode(bool fmp_enable); // Moves the Slave to dedicated pins ``` This enables the "Dual Mode" which moves the slave functionality to a second pair of pins, such that there is a SCL/SDA pair for the master and an SCL/SDA pair for the slave. Some parameters (such as Fast Mode+ support) can be enabled separately for the slave. This must be called before `Wire.begin()` This is only available on megaAVR 0-series, and AVR Dx and Ex-series, not tinyAVR. The version of this document included with such parts will list the slave mode pin-sets. This will generate an error if referenced on a tinyAVR. +#### `uint8_t specialConfig()` ```c++ -specialConfig(bool smbuslvl = 0, bool longsetup = 0, uint8_t sda_hold = 0, bool smbuslvl_dual = 0, uint8_t sda_hold_dual = 0); +uint8_t specialConfig(bool smbuslvl = 0, bool longsetup = 0, uint8_t sda_hold = 0, bool smbuslvl_dual = 0, uint8_t sda_hold_dual = 0); ``` New in 2.6.2/1.5.0 There are up to three options currently supported that tweak behavior as regarding time and voltage levels. Most users should have no need to use these, but they are sometimes needed for compatibility particularly with strange voltage levels. @@ -220,26 +235,28 @@ There are up to three options currently supported that tweak behavior as regardi Feature | TinyAVR 0/1/2 | megaAVR 0 | Dx/Ex | -------------|---------------|-----------|-------| SMBus levels | No | No | Yes | -Long setup | Yes | Yes | Yes | -SDA hold | Yes, no dual | Yes | Yes* | +Long setup`**`|Yes | Yes | Yes | +SDA hold | Yes, no dual | Yes | Yes`*`| `*` This option on the DA-series is impacted by errata on 128k parts: two values are swapped. +`**` The setup time applies to the slave when the slave is stretching the clock. **SMBus levels** -Enabling this (Dx, possibly Ex only) changes the input levels, making them drastically lower, much like the TTL input level option available on some parts. Thresholds as always are the maximum voltage that is still low enough to guarantee will read as low, and the minimum voltage guaranteed to be a high. These normally depend on Vdd - in SMBus voltage level mode, they do not. This is very useful for communicating with lower voltage devices - this is something that is most useful on either the DA (where there is no MVIO) or when the system contains three different voltage levels (say, the AVR running at 5v, using MVIO to talk to a 3.3v device, wishing to use some 1.8V I2C devices). +Enabling this (Dx, Ex only) changes the input levels, making them drastically lower, much like the TTL input level option available on DB, DD, and Ex-series parts. Thresholds as always are the maximum voltage that is still low enough to guarantee will read as low, and the minimum voltage guaranteed to be a high. These normally depend on Vdd - in SMBus voltage level mode, they do not. This is very useful for communicating with lower voltage devices - this is something that is most useful on either the DA (where there is no MVIO) or when the system contains three different voltage levels (say, the AVR running at 5v, using MVIO to talk to a 3.3v device, wishing to use some 1.8V I2C devices). Vdd | SMBus levels | 1.8V | 2.5V | 3.3V | 5.0V | ------------------|--------------|-------|-------|-------|------| Vin Low (max) | 0.80V | 0.54V | 0.75V | 0.99V | 1.5V | -Vin High (min) | * 1.35V | 1.26V | 1.75V | 2.31V | 3.5V | +Vin High (min) DB | 1.35V | 1.26V | 1.75V | 2.31V | 3.5V | +Vin High (min) DA/DD | 1.35V or 1.45 `*` | 1.26V | 1.75V | 2.31V | 3.5V | `*` The DA and DD (but not the DB) datasheets imply that the minimum guaranteed high is 1.45V in SMbus mode if running at less than 2.5v. Whether this is an issue with the documentation or an actual difference is unclear. **SDA setup and hold times** -The setup time for SDA can be either 4 or 8 cycles. May be required for compatibility with some unusual devices. +The setup time for SDA can be either 4 or 8 cycles. May be required for compatibility with some unusual devices. This refers specifically to operation in slave mode -The hold time can be turned off, or set to 50, 300 or 500 ns. A non-default option is required to comply with SMBus protocol. +The hold time can be turned off, or set to 50, 300 or 500 ns. A non-default option is required to comply with SMBus protocol. 300 and 500 are backwards on AVR128DA parts until some future die rev that corrects it. We do not attempt an errata check - this is a very niche feature, and only AVR128DA parts are impacted (AVR64DA and AVR128DB are both fine) **Dual mode options** Both the voltage levels and the hold times can be configured for the dual mode pins independently from the master/slave pins on parts with those features. @@ -259,7 +276,7 @@ Both the voltage levels and the hold times can be configured for the dual mode p #define WIRE_SMBUS_LEVELS 1 ``` -This method should always return 0 (success). We try to error if compiletime-known invalid values are passed. During development, you should be sure to check this value to make sure that +This method should always return 0 (success). We try to error if compiletime-known invalid values are passed. During development, you should be sure to check this value to make sure that you passed valid arguments to it if you are having any issues, or are concerned that the test conditions might not reveal them. If not: @@ -267,28 +284,31 @@ If not: |--------------|-----------------------------------------------------------| | 0x00 | Successful | | 0x01 | SMBus level requested on part without that feature | -| 0x02 | Dual mode options passed. Part does not support dual mode | +| 0x02 | Dual mode options passed - but part does not support dual mode | | 0x04 | Dual mode options passed, dual mode present, but not enabled. Refer to startup order. | | 0x08 | Invalid sda hold value passed (must be 0 - 3 or one of the named cosntants). The default is used instead | Linear combinations are possible; 0x0B indicates that you asked for SMBus levels, and one or more dual mode options on a tinyAVR which supports none of those things, and you passed an invalid value for the SDA hold times, 0x05 indicates you asked for SMBus levels and one or more dual mode options without first enabling dual mode, on a megaAVR 0-series which does not support SMBus, and so on. +Note: These options are each applicable to a different subset of conditions. They're mashed under one heading because they're all the ones that made no sense anywhere else + ### Standard methods and features significant differences +#### `void begin()` ```c++ -begin(); +void begin(); ``` Calling `begin()` with no arguments starts the master. It does not start slave mode. It does NOT turn on the pullups on any pin - unlike the standard version. ```c++ -begin(uint8_t address, bool receive_broadcast = 0, uint8_t second_address = 0); +void begin(uint8_t address, bool receive_broadcast = 0, uint8_t second_address = 0); ``` This starts the slave Wire functionality. *It does not start master functionality - when both are enabled, begin() and begin(address) must be called*. The second and third arguments are optional. The first argument simply specifies the slave address to listen on (like on standard `Wire.begin()`), the second argument enables receiving of general call addresses, and the third allows specification of either a second address, or a mask. If receive_broadcast is true, the handler selected by `Wire.onReceive()` will be called when a "General Call" message is seen, containing the data or command included with it. General call commands are always writes, perhaps obviously (since more than one device attempting to respond would result in nothing but gibberish). According to the specification, a general call is followed by a 1 byte command, either 0x06 or 0x04 (0x00 is forbidden). The commands instruct the slave to use a previously programmed address, where 0x06 also instructs the slave to do a software-reset (not implemented by Wire library). If not specified, this defaults to `false`; it must be specified if the third argument is used. -If second_address is supplied, two options can be used. In both cases a 7 bit address is supplied in the largest 7 bits (that is, left-shifted once from the traditional Arduino representation); it's function is controlled by the the least significant bit - if the LSB is 1, it's a second address matched in addition to the first. Otherwise, bits that are 1's are not masked off, and are not considered. Two helper macros are provided - these are meant for the sole purpose of code readability - the macro names are self explanatory. +If second_address is supplied, two options can be used. In both cases a 7 bit address is supplied in the largest 7 bits (that is, left-shifted once from the traditional Arduino representation); it's function is controlled by the the least significant bit - if the LSB is 1, it's a second address matched in addition to the first. Otherwise, bits that are 1's are masked off, and are not considered. Two helper macros are provided - these are meant for the sole purpose of code readability - the macro names are self explanatory. ```c++ #define WIRE_ALT_ADDRESS(alt_address) ((alt_address << 1) | 0x01) #define WIRE_ADDRESS_MASK(mask) (mask << 1) @@ -303,8 +323,9 @@ When the second or third argument was used, `Wire.getIncomingAddress()` should b If (and only if) the Master and Slave option is selected in the Tools -> Wire mode, the Wire interface can be enabled for both master and slave. Even when Dual Mode is used, the correct option must still be selected to enable acting as both master and slave. +#### `void setClock()` ```c++ -setClock(uint32_t); +void setClock(uint32_t); ``` The `setClock()` method has it's usual function. `Wire.setClock()` is not exact. The hardware clock generator monitors the SCL line, and begins the next pulse only after the pin has returned to HIGH and been there for a requisite amount of time. The length of these times, thigh and tlow are controlled by the `TWIn.MBAUD` register, which is what setClock() changes. But the period of each cycle is composed of 4 parts: thigh +tfall + tlow + fall. the high and low times are controlled by this register, and tFall is influenced by whether the part is set to "FM+ mode" (this drives the pin harder). As described above, the factor limiting the speed of I2C as the speed gets faster is the rise time, which is controlled solely by the total strength of all the pullups on the bus, and the capacitance of the bus. Since the baud generator adapts to electrical conditions, which are not known to the software, this clock setting would only match with one combination of pullup strength and bus capacitance, and at the extremes, the difference in clock speed with the same baud rate set but different electrical conditions on the bus could be 50% or more. *This is preferable to the alternative approach of ignoring the electrical conditions, setting a fixed clock speed, and failing to transfer data under adverse electrical conditions*. @@ -329,21 +350,35 @@ Remember that the SCL frequency is generated by a divider, so at lower CPU frequ High Speed Mode is different animal altogether: FM+ more or less exhausted what could be achieved with a purely open drain bus, and of course, folks still demanded a faster bus. High Speed mode added a current source, and these devices usually separate HS from non-HS devices, using separate pins for each. No AVR device so far released supports HS I2C, and when such speeds are necessary, SPI is a better solution. -`Wire.setClock()` has been varying degrees of broken for most of the history of megaTinyCore. Users [@rneurink](https://github.com/rneurink) and [@MX682X](https://github.com/MX682X) made contributions and since 2.3.3 it has been reasonably close to correct. The old library was kind of a dumpster fire - this was far from the only problematic area of it. In a later version, the frequency accuracy was improved. +`Wire.setClock()` has been varying degrees of broken for most of the history of megaTinyCore. Users [@rneurink](https://github.com/rneurink) and [@MX682X](https://github.com/MX682X) made contributions and since 2.3.3 it has been reasonably close to correct. The old library was kind of a dumpster fire - this was far from the only problematic area of it. In a later version, the frequency accuracy was improved. The latter also went on to perform most of the near total rewrite of Wire. + +`Wire.setClock()` has no effect in slave mode as you do not have control of the clock. +#### `void flush()` ```c++ -flush(); +void flush(); ``` -~A `flush()` method exists on all versions of Wire.h; indeed, `Stream` which it subclasses demands that - however very rarely is it actually implemented by anything other than Serial - (where there is a specific and very common reason use case) where one must clear the buffer in a specific way (by waiting for it to empty) before doing things like going to sleep or performing a software reset. Wire has rarely implemented this. In this case, it performs the functionality that the datasheets refer to as a "TWI_FLUSH" - this resets the internal state *of the master* - and at the Wire library level, the buffers are cleared; that command is apparently intended for error handling. The hardware keeps track of activity on the bus (as required by the protocol), but misbehaving devices can confuse the master - they might do something that the specification says a device will will not do, or generate electrical conditions that the master is unable to interpret in a useful way - pins not reaching the logic level thresholds, malformed data and which may in turn leave it confused as to whether the bus is available. It might be necessary to call in an attempt to recover from adverse events, which has historically been a challenge for the Wire library.~ +~A `flush()` method exists on all versions of Wire.h; indeed, `Stream` which it subclasses demands that - however very rarely is it actually implemented by anything other than Serial - (where there is a specific and very common use case for the concept that that flush function exposes. That is not the same as what the TWI flush command does. Where one must clear the buffer in a specific way (by waiting for it to empty) before doing things like going to sleep or performing a software reset, Serial.flush() is just the ticket. Wire has rarely implemented such a tool. If it weren't for an erratum, there would be an available command that the datasheets refer to as a "TWI_FLUSH" - this resets the internal state *of the master* - and at the Wire library level, the buffers wouldneed to be cleared too. That command is apparently intended for error handling. The hardware keeps track of activity on the bus (as required by the protocol), but misbehaving devices can confuse the master - they might do something that the specification says a device will will not do, or generate electrical conditions that the master is unable to interpret in a useful way - pins not reaching the logic level thresholds, malformed data and which may in turn leave it confused as to whether the bus is available. It might be necessary to call in an attempt to recover from adverse events, which has historically been a challenge for the Wire library.~ There are two issues that lead to this method being a stub (do-nothing method, required to subclass stream). First is the tension with between the hardware "flush" functionality, which according to the datasheet is intended to clear up detected bus errors, with the arduino API for HardwareSerial, the only stream where flush is generally implemented, where it instead waits for outgoing transmissions to finish. Secondly, either all, or all non-tinyAVR parts appear likely to be impacted by an erratum that renders the hardware mechanism less than useful: "Flush Non-Functional", which according to the errata sheets can in practice cause the very problem it was intended to solve, and advising us to disable and re-enable TWI master instead. -So, using the hardware's flush function to clear the master's state is out. One can also argue that because of the precedent set by HardwareSerial, we should defer to it's well-defined behavior over the foggy definition of the arduino Stream API, and have it mimic that functionality. But in that case, for the I2C master, the methods to send data already block until the operation is completed, and on the slave side, the device is at the mercy of the master - we could be waiting literally an eternity (that is, it is equivalent to using Serial.flush() when acting as a slave device in synchronous mode to transmit when the XCK pin has no clock signal - this is essentially the same situation with a different interface. Waiting for data to be clocked out, without control over that clock, is probably not the ideal API to provide either; the same behavior can be achieved with `while (slaveTransactionOpen()) {...}`, but this gives an opportunity to have a timeout after which the slave can decide that the master has fallen over and (for example) disable and re-enable the interface, expecting that the next contact from the master will be a fresh start condition from a reinitialized master. +It raises some very thorny questions: + +Most fundamental is the simplest question to ask about the Stream.Flush that subclasses are required to implement: What does it do? + +This is well-defined only for serial ports, where it specifically means "wait until the transmit buffer is done transmitting". This generalizes very poorly to other Streams, including Wire, and is greatly complicated by the fact Microchip has it's own idea of what "Flush" means for TWI (although it doesn't work on most parts that have it). The + +But this meaning is not entirely generalizable to the typical subclasses of Stream - such as two-wire. Is it supposed to wait until all data is sent, knowing that this was a do-nothing function except in slave mode? We believe there are significant pitfalls to attempting to implement it in this way, hence the implementation described in this document. In any event: If there isn't one definition, and we're just shoehorning different functionality appropriate for use at different times into one keyword, that's terrible design practice, goes against the concepts of object oriented programming, sanity, and common sense. The Stream class is poorly designed an insufficiently specified, have an abstract method that only makes sense in the context of . On the other hand, if it has a consistent meaning, everything had better implement it and have it do the same thing. + +One can also argue that because of the precedent set by HardwareSerial, we should defer to it's well-defined behavior over the foggy definition of the arduino Stream API, and have it mimic that functionality. But in that case, for the I2C master, the methods to send data already block until the operation is completed, and on the slave side, the device is at the mercy of the master - we could be waiting literally an eternity (that is, it is equivalent to using Serial.flush() when acting as a slave device in synchronous mode to transmit when the XCK pin has no clock signal - this is essentially the same situation with a different interface. If the master has died, reset, crashed or otherwise malfunctioned during a transaction you'd wait forever - waiting for data to be clocked out without control over that clock is generally a Bad Thing. the same behavior can be achieved with `while (slaveTransactionOpen()) {...}`, but this gives an opportunity to have a timeout after which the slave can decide that the master has fallen over and (for example) disable and re-enable the interface, expecting that the next contact from the master will be a fresh start condition from a reinitialized master. It is not hard to imagine a scenario where that would be relevant: Just consider the case of writing application code for the master, and uploading a new version while it happens to be in the middle of a transaction. You probably do not want to wait for the (reset) master to attempt (and fail at, because it would get a bus arbitration error as the slave attempted to answer at the same time the master tried to clock out the address) communication before doing anything, as you'd likely then have to reset both devices at once to upload - sometimes. If you're waiting on the completion of transactions before taking some action, you probably want to make sure you are handling the case where the master does not finish the transaction for some reason. I2C masters rarely take long periods of time between clocking in bytes when functioning correctly. At 100kHz, a byte takes under 100 us to transfer, and the master (if it's another Arduino device) is likely blocking during that time, waiting to grab another byte immediately. Even with a 130 byte buffer, this means that after a dozen ms, it should have been able to receive an entire buffer. If you were to wait 20 ms (or somewhat longer if you specifically modify the library for a massive buffer), you could conclude that the master is unlikely to ever finish. On the other hand, when the only think you intend to do is put the part to sleep, you can attempt to sleep on every pass through loop() instead as described below. On the third hand, if the system were battery powered, and that occurred, the slave would still be in a bad state, consuming IDLE sleep mode current, instead of POWER_DOWN current, which is many times lower, so maybe this would not be so great after all. In any event, a flush that worked like hardware serial would not help that case either, but rather make it drain the battery about twice as fast by not sleeping at all. This brings discussion back to the point I mentioned near the beginning: When something goes wrong with I2C, it usually goes very wrong. Unlike SPI, where you have a pin state to watch to see if the master thinks you are in a transaction, there is nothing physical that you can monitor and notice that the master vanished in the middle of a transaction. + + +#### `uint8_t endTransmission()` ```c++ uint8_t endTransmission(bool sendStop); ``` @@ -394,7 +429,7 @@ The implementation isn't identical, but the behaviour is unchanged, or is differ 1. Master generates start condition. 2. Master clocks out the slave address with read-bit set 3. Slave detects and stretches the clock. -4. Slave fires onReceive handle. +4. Slave fires onRequest handler. 5. In `onRequest` handler, stages all the data the master can read at this time (up to the size of the Wire Buffer) noted above. 6. Slave releases clock and ack's. 7. Master clocks in 1 byte. Slave interrupt fires *silently* after each byte to prepare the next byte and the master ACKs each one before finally NACKing when done and generating a stop condition. @@ -422,6 +457,6 @@ I would also suggest testing this with `Wire.specialConfig(0,1,3)` with the defa At least two of the rewrites of the clock calculation algorithm that have been submitted, independently, by two different individuals (the second of whom didn't stop there and rewrote the whole library, slashing it's flash footprint while adding functionality) were prompted initially by users trying to get this specific part to work. So - really we're all in debt to this manufacturer, whose combination of compelling hardware saddled with a badly botched I2C interface has led to so much improvement in our Wire library. ## Why are there so many names for this protocol? -Wire, TWI (Two Wire Interface), Two Wire, IIC, I2C-compatible, I2C, I2C... The reason for this is that I2C (and the explicitly formatted version of it, I2C) are trademarked by Phillips (now NXP) which has historically been very litigious, and would go after manufacturers of parts that didn't pay license fees. So devices that could communicate with and which were I2C in all but name proliferated. The last patent expired a while ago, but they still hold the trademarks, so other manufacturers persist in using their names. The actual terms described in the specification claim to cover to all devices that "can" communicate over I2C, and make exception only for FPGAs, where the person programming them also was supposed to get a license. It seems that the litigation wars have cooled somewhat now, though I still would be mighty careful if I was designing microcontrollers. In any event, Atmel always called it TWI, and that tradition was not lost when Microchip purchased them. The original name, I2C is a stylization of "IIC", for "Inter-Integrated-Circuit". +Wire, TWI (Two Wire Interface), Two Wire, IIC, I2C-compatible, I2C, I2C even occasionally stylized as I2C... The reason for this is that I2C (and the explicitly formatted version of it, I2C) are trademarked by Phillips (now NXP). Historically control of the I2C spec has been a means of shakihg down other companies, and patent and trademark enforcement has been extremely aggressive, and would go after manufacturers of parts that didn't pay license fees. So devices which were capable of I2C in all but name proliferated, and the copyright holders argument that any manufacturer of a chip that could do I2C or could even *bitbang it* (in other words, essentially every microcontroller) had to give them money didn't hold up in court. The last patent expired a while ago, but they still hold the trademarks, so other manufacturers persist in using their names. The actual terms described in the specification claim to cover to all devices that "can" communicate over I2C, and make exception only for FPGAs, where the person programming them also was supposed to get a license. It seems that the litigation wars have cooled somewhat now, though I still would be mighty careful if I was designing microcontrollers. In any event, Atmel always called it TWI, and that tradition was not lost when Microchip purchased them. The original name, I2C is a stylization of "IIC", for "Inter-Integrated-Circuit". Just don't get it confused with I2S (that's a specialized protocol for real-time transmission uncompressed digital audio - for example, it is (or was) often used in CD players between the disk reading circuitry and the DAC - as well as in professional audio equipment. I2S isn't supported by the AVR line - it's not a good match for the capabilities or intended use cases of AVR devices; it's for dedicated audio stuff, not general purpose microcontrollers). or I3C (a much faster superficially similar successor meant for much faster parts with far more computational power - also not supported by AVR). Even calling it superficially similar is being rather generous, as the resemblance seems to be largely window dressing: there is a data line called SDA and a clock line called SCL, devices can be master or slave, and the slave devices have 7 bit addresses - and that's about the end of the resemblance. While the I3C standard claims backwards compatibility, it does not support clock stretching (which it calls "rarely used" - I'm not sure what gave them that idea, but the initiative is being spearheaded by Google, so it should surprise nobody that they are out of touch with reality). An AVR acting as slave could not even reach user onRequest or onReceive code even if the I3C bus was running at only 1 MHz. It runs at 12.5 MHz - in other words, these parts are between one and two orders of magnitude too slow to meet the timing constraints of I3C). diff --git a/megaavr/libraries/Wire/src/twi_pins.c b/megaavr/libraries/Wire/src/twi_pins.c index 75b155c1..7a828b9b 100644 --- a/megaavr/libraries/Wire/src/twi_pins.c +++ b/megaavr/libraries/Wire/src/twi_pins.c @@ -193,7 +193,8 @@ bool TWI0_Pins(uint8_t sda_pin, uint8_t scl_pin) { twimux |= PORTMUX_TWI0_ALT1_gc; PORTMUX.TWISPIROUTEA = twimux; return true; - /* end can'thappen */ + } + /* end can'thappen */x #endif } else if (sda_pin == PIN_WIRE_SDA && scl_pin == PIN_WIRE_SCL) { // Use default configuration @@ -214,9 +215,13 @@ bool TWI0_Pins(uint8_t sda_pin, uint8_t scl_pin) { } else #endif #if defined(PIN_WIRE_SDA_PINSWAP_2) + #if !defined(ERRATA_TWI0_MUX2) if (sda_pin == PIN_WIRE_SDA_PINSWAP_2 && scl_pin == PIN_WIRE_SCL_PINSWAP_2) { PORTMUX.TWIROUTEA = portmux | PORTMUX_TWI0_ALT2_gc; return true; + #else + return false; + #endif } else #endif #if defined(PIN_WIRE_SDA_PINSWAP_1) @@ -299,8 +304,12 @@ bool TWI0_swap(uint8_t state) { #if defined(PIN_WIRE_SDA_PINSWAP_2) if (state == 2) { // Use pin swap + #if !defined(ERRATA_TWI0_MUX2) PORTMUX.TWIROUTEA = portmux | PORTMUX_TWI0_ALT2_gc; return true; + #else + return false; + #endif } else #endif #if defined(PIN_WIRE_SDA_PINSWAP_1) @@ -565,6 +574,7 @@ void TWI1_ClearPins() { } #endif #endif + (void) portmux; //this is grabbed early, but depending on which part and hence what is conditionally compiled, may not go into the code. It will produce spurious warnings without this line } @@ -685,6 +695,7 @@ void TWI1_usePullups() { } } #endif + (void) portmux; //this is grabbed early, but depending on which part and hence what is conditionally compiled, may not go into the code. It will produce spurious warnings without this line } //Check if TWI1 Master pins have a HIGH level: Bit0 = SDA, Bit 1 = SCL diff --git a/megaavr/libraries/Wire/src/twi_pins.h b/megaavr/libraries/Wire/src/twi_pins.h index eb85791c..6bb8493b 100644 --- a/megaavr/libraries/Wire/src/twi_pins.h +++ b/megaavr/libraries/Wire/src/twi_pins.h @@ -41,6 +41,15 @@ // only work on some parts - things like TWI1_swap() which has only 1 option on some parts. #endif + +#if defined(PORTMUX_TWISPIROUTEA) + // All of these are mega zero's. Hence they have 3 options. + #if !defined(PIN_WIRE_SDA_PINSWAP_2) && defined(PIN_WIRE_SDA_PINSWAP_1) + #define PIN_WIRE_SDA_PINSWAP_2 PIN_PC2 + #define PIN_WIRE_SCL_PINSWAP_2 PIN_PC3 + #endif +#endif + void TWI0_ClearPins(); bool TWI0_Pins(uint8_t sda_pin, uint8_t scl_pin); bool TWI0_swap(uint8_t state); diff --git a/megaavr/libraries/tinyNeoPixel/library.properties b/megaavr/libraries/tinyNeoPixel/library.properties index 5a308b4f..d8b83a77 100644 --- a/megaavr/libraries/tinyNeoPixel/library.properties +++ b/megaavr/libraries/tinyNeoPixel/library.properties @@ -1,9 +1,9 @@ name=tinyNeoPixel -version=2.0.5 +version=2.0.7 author=Adafruit (modified by Spence Konde) maintainer=Spence Konde sentence=Arduino library for controlling single-wire-based LED pixels and strip for all modern (post 2016) AVR microcontrollers, and distributed with megaTinyCore and DxCore. -paragraph=This library is closely based on the original Adafruit_NeoPixel library. It has been modified to account for the improved ST performance on the tinyAVR 0-series, tinyAVR 1-series and megaAVR 0-series, and add support for speeds from 4 MHz to 48 MHz. No specific actions to choose the port the port at any speed (enabled by ST improvements). Please refer to the documentation for more information.
2.0.5 - correct some timing and compile issues at certain speeds.
2.0.4 - Add support for operation at lower speeds, possibkly as low as 4 MHz. Ensure that the inline assembly is specified correctly.
2.0.3 - Fix issue when millis is disabled. +paragraph=This library is closely based on the original Adafruit_NeoPixel library. It has been modified to account for the improved ST performance on the tinyAVR 0-series, tinyAVR 1-series and megaAVR 0-series, and add support for speeds from 4 MHz to 48 MHz. No specific actions to choose the port the port at any speed (enabled by ST improvements). Please refer to the documentation for more information.
2.0.7 - Fix critical defect in 10 and 12 MHz implementations which would output the first bit only.
2.0.6 - correct naming of labels in asm to conform with our naming policy. Add show(number), which will show up to the first (number) leds. This allows the static allocation version to light up a varying number of LEDs. 2.0.5 - correct some timing and compile issues at certain speeds.
2.0.4 - Add support for operation at speeds as low as 4 MHz. Ensure that the inline assembly is specified correctly.
2.0.3 - Fix issue when millis is disabled. category=Display url=https://github.com/SpenceKonde/megaTinyCore/blob/master/megaavr/extras/tinyNeoPixel.md architectures=megaavr diff --git a/megaavr/libraries/tinyNeoPixel/tinyNeoPixel.cpp b/megaavr/libraries/tinyNeoPixel/tinyNeoPixel.cpp index ffa39170..1fba1f42 100644 --- a/megaavr/libraries/tinyNeoPixel/tinyNeoPixel.cpp +++ b/megaavr/libraries/tinyNeoPixel/tinyNeoPixel.cpp @@ -50,7 +50,7 @@ // Constructor when length, pin and type are known at compile-time: tinyNeoPixel::tinyNeoPixel(uint16_t n, uint8_t p, neoPixelType t) : - begun(false), brightness(0), pixels(NULL), endTime(0), latchTime(50) { + begun(false), brightness(0), pixels(NULL), latchTime(50), endTime(0) { updateType(t); updateLength(n); setPin(p); @@ -63,7 +63,7 @@ tinyNeoPixel::tinyNeoPixel(uint16_t n, uint8_t p, neoPixelType t) : // updateLength(), etc. to establish the strand type, length and pin number! tinyNeoPixel::tinyNeoPixel() : begun(false), numLEDs(0), numBytes(0), pin(NOT_A_PIN), brightness(0), pixels(NULL), - rOffset(1), gOffset(0), bOffset(2), wOffset(1), endTime(0), latchTime(50) { + rOffset(1), gOffset(0), bOffset(2), wOffset(1), latchTime(50), endTime(0) { } tinyNeoPixel::~tinyNeoPixel() { @@ -118,12 +118,27 @@ void tinyNeoPixel::updateType(neoPixelType t) { } // *INDENT-OFF* astyle don't like assembly -void tinyNeoPixel::show(void) { +#if (PROGMEM_SIZE > 4096UL) +void tinyNeoPixel::show(void) { + show(0); +} +void tinyNeoPixel::show(uint16_t leds) { + volatile uint16_t i = numBytes;; + if (!(numLEDs <= leds || !leds)) { + if (wOffset == rOffset) { + i = leds + leds + leds; + } else { + i = leds << 2; + } + } if ((!pixels) || pin >= NUM_DIGITAL_PINS) { return; } - +#else + void tinyNeoPixel::show(void) { + volatile uint16_t i = numBytes; // Loop counter +#endif // Data latch = 50+ microsecond pause in the output stream. Rather than // put a delay at the end of the function, the ending time is noted and // the function will simply hold off (if needed) on issuing the @@ -146,14 +161,14 @@ void tinyNeoPixel::show(void) { // to the PORT register as needed. // Notes (Spence 2/2022): // OUTs suck, because they force duplication of the routine to handle - // different ports. Luckily we don't need them here - modern AVs are + // different ports. Luckily we don't need them here - modern AVRs are // based on the AVRxt core, and ST takes only 1 clock. // Actually, it is possible to do that on the classic AVR version of // these routines too... I'm not sure why it wasn't done, other than // a strict adherence to the official datasheet, unhindered by any // actual testing of how the parts behave (a LOW of only 6 us is // enough to trigger the latch, that is the only limit on the length - // of a LOW, (hence if any bit takes longer than 5 bits are supposed + // of a LOW, (hence if any bit takes longer than 5 bytes are supposed // to, it won't work :-P)), and the length of a 1 is limited by the // fact that it will get "signal reshaped" an "ideal length". If this // results in the following LOW exceeding the latch limit, it breaks. @@ -161,21 +176,21 @@ void tinyNeoPixel::show(void) { // So, having timing that's not strictly speaking correct located in // anywhere that won't make a 0 look like a 1 or the other way around, // you can kind of get away with a lot. - + // // This code manages to avoid having having to do that all the way down // to 4-5 MHz one. which takes 1400 us to run for 6 bits and 1600 for the // last two. // // Over the years a bunch of faster speeds have been added, so it could - // be used even on highly overclocked modern AVFs. + // be used even on highly overclocked modern AVRs. // // Finally, some of the constraints were incorrect, and this only ever // worked because classes are kryptonite to the optimizer. // * [ptr] was decleared read only. No, it is not. The register is // register contaains the address being pointed to. We read with // postincrement, so this is read-write. - // * Comversely, [port] is never written. The thing that port writes - // *too* is changd, but [port] is not. + // * Conversely, [port], another pointer, is never written. The thing that + // [port] points *to* is changed, but [port] is not. // * b (bit number for speeds that don't have to unroll the loop) // is given constraint "+r", which can assign it to any register. // But the code uses LDI on it. LDI doesn't work on every register, @@ -190,8 +205,7 @@ void tinyNeoPixel::show(void) { // run at those speeds, only that - if they do - you can control WS2812s // with them. - volatile uint16_t - i = numBytes; // Loop counter + volatile uint8_t *ptr = pixels, // Pointer to next byte b = *ptr++, // Current byte value @@ -563,9 +577,10 @@ void tinyNeoPixel::show(void) { "rcall _bitTime10" "\n\t" // Bit 1 // Bit 0: "st %a[port], %[hi]" "\n\t" // 1 PORT = hi (T = 1) - "rjmp .+0" "\n\t" // 2 nop nop (T = 3) - "ld %[byte] , %a[ptr]+" "\n\t" // 2 b = *ptr++ (T = 5) - "st %a[port], %[next]" "\n\t" // 1 PORT = next (T = 6) + "nop" "\n\t" // 1 nop (T = 2) + "ld %[byte] , %a[ptr]+" "\n\t" // 2 b = *ptr++ (T = 4) + "st %a[port], %[next]" "\n\t" // 1 PORT = next (T = 5) + "nop" "\n\t" // 1 nop (T = 6) "mov %[next] , %[lo]" "\n\t" // 1 next = lo (T = 7) "sbrc %[byte] , 7" "\n\t" // 1-2 if (b & 0x80) (T = 8) "mov %[next] , %[hi]" "\n\t" // 0-1 next = hi (T = 9) @@ -576,10 +591,11 @@ void tinyNeoPixel::show(void) { "_bitTime10:" "\n\t" // nop nop nop (T = 4) "st %a[port], %[next]" "\n\t" // 1 PORT = next (T = 5) "mov %[next], %[lo]" "\n\t" // 1 next = lo (T = 6) - "rol %[byte]" "\n\t" // 1 b <<= 1 (T = 7) + "lsl %[byte]" "\n\t" // 1 b <<= 1 (T = 7) "sbrc %[byte], 7" "\n\t" // 1-2 if (b & 0x80) (T = 8) "mov %[next], %[hi]" "\n\t" // 0-1 next = hi (T = 9) "st %a[port], %[lo]" "\n\t" // 1 PORT = lo (T = 10) + "ret" "\n\t" // 4 return to above where we called from "_done10:" "\n" : [ptr] "+e" (ptr), [byte] "+r" (b), @@ -594,10 +610,6 @@ void tinyNeoPixel::show(void) { // 12 MHz(ish) AVRxt -------------------------------------------------------- #elif (F_CPU >= 11100000UL) && (F_CPU <= 14300000UL) - // In the 12 MHz case, an optimized 800 KHz datastream (no dead time - // between bytes) requires a PORT-specific loop similar to the 8 MHz - // code (but a little more relaxed in this case). - // 15 instruction clocks per bit: HHHHxxxxxxLLLLL H:4 x:6 L5 // OUT instructions: ^ ^ ^ (T=0,4,10) @@ -630,13 +642,13 @@ void tinyNeoPixel::show(void) { "rcall _bitTime12" "\n\t" // Bit 1 // Bit 0: "st %a[port], %[hi]" "\n\t" // 1 PORT = hi (T = 1) - "rjmp .+0" "\n\t" // 2 nop nop (T = 3) - "ld %[byte] , %a[ptr]+" "\n\t" // 2 b = *ptr++ (T = 5) - "st %a[port], %[next]" "\n\t" // 1 PORT = next (T = 6) - "mov %[next] , %[lo]" "\n\t" // 1 next = lo (T = 7) - "sbrc %[byte] , 7" "\n\t" // 1-2 if (b & 0x80) (T = 8) - "mov %[next] , %[hi]" "\n\t" // 0-1 next = hi (T = 9) - "nop" "\n\t" // 1 (T = 10) + "nop" "\n\t" // 1 nop (T = 2) + "ld %[byte] , %a[ptr]+" "\n\t" // 2 b = *ptr++ (T = 4) + "st %a[port], %[next]" "\n\t" // 1 PORT = next (T = 5) + "mov %[next] , %[lo]" "\n\t" // 1 next = lo (T = 6) + "sbrc %[byte] , 7" "\n\t" // 1-2 if (b & 0x80) (T = 7) + "mov %[next] , %[hi]" "\n\t" // 0-1 next = hi (T = 8) + "rjmp .+0" "\n\t" // 2 nop nop (T = 10) "st %a[port], %[lo]" "\n\t" // 1 PORT = lo (T = 11) "sbiw %[count], 1" "\n\t" // 2 i-- (T = 13) "brne _head12" "\n\t" // 2 if (i != 0) -> (next byte) @@ -644,11 +656,12 @@ void tinyNeoPixel::show(void) { "_bitTime12:" "\n\t" // nop nop nop (T = 4) "st %a[port], %[next]" "\n\t" // 1 PORT = next (T = 5) "mov %[next], %[lo]" "\n\t" // 1 next = lo (T = 6) - "rol %[byte]" "\n\t" // 1 b <<= 1 (T = 7) + "lsl %[byte]" "\n\t" // 1 b <<= 1 (T = 7) "sbrc %[byte], 7" "\n\t" // 1-2 if (b & 0x80) (T = 8) "mov %[next], %[hi]" "\n\t" // 0-1 next = hi (T = 9) "nop" "\n\t" // 1 (T = 10) "st %a[port], %[lo]" "\n\t" // 1 PORT = lo (T = 11) + "ret" "\n\t" // 4 return to above where we called from "_done12:" "\n" : [ptr] "+e" (ptr), [byte] "+r" (b), @@ -676,7 +689,7 @@ void tinyNeoPixel::show(void) { bit = 8; asm volatile( - "head20:" "\n\t" // Clk Pseudocode (T = 0) + "_head16:" "\n\t" // Clk Pseudocode (T = 0) "st %a[port], %[hi]" "\n\t" // 1 PORT = hi (T = 1) "nop" "\n\t" // 1 nop (T = 2) "sbrc %[byte], 7" "\n\t" // 1-2 if (b & 128) @@ -685,21 +698,21 @@ void tinyNeoPixel::show(void) { "st %a[port], %[next]" "\n\t" // 1 PORT = next (T = 6) "nop" "\n\t" // 1 nop (T = 7) "mov %[next] , %[lo]" "\n\t" // 1 next = lo (T = 8) - "breq nextbyte20" "\n\t" // 1-2 if (bit == 0) (from dec above) + "breq _nextbyte16" "\n\t" // 1-2 if (bit == 0) (from dec above) "rol %[byte]" "\n\t" // 1 b <<= 1 (T = 10) "rjmp .+0" "\n\t" // 2 nop nop (T = 12) "nop" "\n\t" // 1 nop (T = 13) "st %a[port], %[lo]" "\n\t" // 1 PORT = lo (T = 14) "rjmp .+0" "\n\t" // 2 nop nop (T = 16) "rjmp .+0" "\n\t" // 2 nop nop (T = 18) - "rjmp head20" "\n\t" // 2 -> head20 (next bit out) (T=20) - "nextbyte20:" "\n\t" // (T = 10) + "rjmp _head16" "\n\t" // 2 -> _head16 (next bit out) (T=20) + "_nextbyte16:" "\n\t" // (T = 10) "ldi %[bit] , 8" "\n\t" // 1 bit = 8 (T = 11) "ld %[byte] , %a[ptr]+" "\n\t" // 2 b = *ptr++ (T = 13) "st %a[port], %[lo]" "\n\t" // 1 PORT = lo (T = 14) "rjmp .+0" "\n\t" // 2 nop nop (T = 16) "sbiw %[count], 1" "\n\t" // 2 i-- (T = 18) - "brne head20" "\n" // 2 if (i != 0) -> (next byte) (T=20) + "brne _head16" "\n" // 2 if (i != 0) -> _head16 (bit 0 of next byte.) (T=20) : [ptr] "+e" (ptr), [byte] "+r" (b), [bit] "+d" (bit), @@ -724,7 +737,7 @@ void tinyNeoPixel::show(void) { bit = 8; asm volatile( - "head20:" "\n\t" // Clk Pseudocode (T = 0) + "_head20:" "\n\t" // Clk Pseudocode (T = 0) "st %a[port], %[hi]" "\n\t" // 1 PORT = hi (T = 1) "sbrc %[byte], 7" "\n\t" // 1-2 if (b & 128) "mov %[next], %[hi]" "\n\t" // 0-1 next = hi (T = 3) @@ -733,17 +746,17 @@ void tinyNeoPixel::show(void) { "rjmp .+0" "\n\t" // 2 nop nop (T = 7) "st %a[port], %[next]" "\n\t" // 1 PORT = next (T = 8) "mov %[next] , %[lo]" "\n\t" // 1 next = lo (T = 9) - "breq nextbyte20" "\n\t" // 1-2 if (bit == 0) (from dec above) + "breq _nextbyte20" "\n\t" // 1-2 if (bit == 0) (from dec above) -> _nextbyte20 "rol %[byte]" "\n\t" // 1 b <<= 1 (T = 11) "rjmp .+0" "\n\t" // 2 nop nop (T = 13) "nop" "\n\t" // 1 nop (T = 14) "st %a[port], %[lo]" "\n\t" // 1 PORT = lo (T = 15) - "rcall onlydelay20" "\n\t" // 2+4+2 = 7 (T = 23) - "rjmp head20" "\n\t" // 2 -> head20 (next bit out T = 25) - "onlydelay20:" "\n\t" // 2 rcall (T = n+2) + "rcall _onlydelay20" "\n\t" // 2+4+2 = 7 (T = 23) + "rjmp _head20" "\n\t" // 2 -> _head20 (next bit out T = 25) + "_onlydelay20:" "\n\t" // 2 rcall (T = n+2) "rjmp .+0" "\n\t" // 2 nop nop (T = n+4) "ret" "\n\t" // 4 4x nop (T = n+8) - "nextbyte20:" "\n\t" // (T = 11) + "_nextbyte20:" "\n\t" // (T = 11) "ldi %[bit] , 8" "\n\t" // 1 bit = 8 (T = 12) "ld %[byte] , %a[ptr]+" "\n\t" // 2 b = *ptr++ (T = 14) "nop" "\n\t" // 1 nop (T = 15) @@ -752,7 +765,7 @@ void tinyNeoPixel::show(void) { "rjmp .+0" "\n\t" // 2 nop nop (T = 19) "rjmp .+0" "\n\t" // 2 nop nop (T = 21) "sbiw %[count], 1" "\n\t" // 2 i-- (T = 23) - "brne head20" "\n" // 2 if (i != 0) -> (next byte) () + "brne _head20" "\n" // 2 if (i != 0) -> _head20 (next byte) : [ptr] "+e" (ptr), [byte] "+r" (b), [bit] "+d" (bit), @@ -779,7 +792,7 @@ void tinyNeoPixel::show(void) { asm volatile( - "head24:" "\n\t" // Clk Pseudocode (T = 0) + "_head24:" "\n\t" // Clk Pseudocode (T = 0) "st %a[port], %[hi]" "\n\t" // 1 PORT = hi (T = 1) "sbrc %[byte], 7" "\n\t" // 1-2 if (b & 128) "mov %[next], %[hi]" "\n\t" // 0-1 next = hi (T = 3) @@ -789,24 +802,24 @@ void tinyNeoPixel::show(void) { "st %a[port], %[next]" "\n\t" // 1 PORT = next (T = 9) "mov %[next] , %[lo]" "\n\t" // 1 next = lo (T = 10) "rjmp .+0" "\n\t" // 2 nop nop (T = 14) - "breq nextbyte24" "\n\t" // 1-2 if (bit == 0) (from dec above) + "breq _nextbyte24" "\n\t" // 1-2 if (bit == 0) (from dec above) -> _nextbyte24 "rol %[byte]" "\n\t" // 1 b <<= 1 (T = 16) "st %a[port], %[lo]" "\n\t" // 1 PORT = lo (T = 17) - "rcall seconddelay24" "\n\t" // 2+4+5=11 (T = 28) - "rjmp head24" "\n\t" // 2 -> head24 (next bit out) - "seconddelay24:" "\n\t" // + "rcall _seconddelay24" "\n\t" // 2+4+5=11 (T = 28) + "rjmp _head24" "\n\t" // 2 -> _head24 (next bit out) + "_seconddelay24:" "\n\t" // "rjmp .+0" "\n\t" // 2 "rjmp .+0" "\n\t" // 2 - "thirddelay24:" "\n\t" + "_thirddelay24:" "\n\t" "nop" "\n\t" // 1 "ret" "\n\t" // 4 - "nextbyte24:" "\n\t" // last bit of a byte (T = 15) + "_nextbyte24:" "\n\t" // last bit of a byte (T = 15) "st %a[port], %[lo]" "\n\t" // 1 PORT = lo (T = 16) "ldi %[bit] , 8" "\n\t" // 1 bit = 8 (T = 17) "ld %[byte] , %a[ptr]+" "\n\t" // 2 b = *ptr++ (T = 19) - "rcall thirddelay24" "\n\t" // 2+4+1=7 (T = 26) + "rcall _thirddelay24" "\n\t" // 2+4+1=7 (T = 26) "sbiw %[count], 1" "\n\t" // 2 i-- (T = 28) - "brne head24" "\n" // 2 if (i != 0) -> (next byte) (T = 30) + "brne _head24" "\n" // 2 if (i != 0) -> _head24 (next byte) (T = 30) : [ptr] "+e" (ptr), [byte] "+r" (b), [bit] "+d" (bit), @@ -834,7 +847,7 @@ void tinyNeoPixel::show(void) { bit = 8; asm volatile( - "head28:" "\n\t" // Clk Pseudocode (T = 0) + "_head28:" "\n\t" // Clk Pseudocode (T = 0) "st %a[port], %[hi]" "\n\t" // 1 PORT = hi (T = 1) "sbrc %[byte], 7" "\n\t" // 1-2 if (b & 128) "mov %[next], %[hi]" "\n\t" // 0-1 next = hi (T = 3) @@ -843,25 +856,25 @@ void tinyNeoPixel::show(void) { "rjmp .+0" "\n\t" // 2 nop nop (T = 8) "st %a[port], %[next]" "\n\t" // 1 PORT = next (T = 9) "mov %[next] , %[lo]" "\n\t" // 1 next = lo (T = 10) - "rcall firstdelay28" "\n\t" // 2+4 = 7 (T = 17) - "breq nextbyte28" "\n\t" // 1-2 if (bit == 0) (from dec above) + "rcall _firstdelay28" "\n\t" // 2+4 = 7 (T = 17) + "breq _nextbyte28" "\n\t" // 1-2 if (bit == 0) (from dec above) -> _nextbyte28 "rol %[byte]" "\n\t" // 1 b <<= 1 (T = 19) "st %a[port], %[lo]" "\n\t" // 1 PORT = lo (T = 20) - "rcall seconddelay28" "\n\t" // 2+4+1+6=11 (T = 31) - "rjmp head28" "\n\t" // 2 -> head20 (next bit out) - "seconddelay28:" "\n\t" // - "rcall thirddelay28" "\n\t" // 2+4=6 saves 2 instruction words vs two rjmp .+0 - "firstdelay28:" "\n\t" // first delay + "rcall _seconddelay28" "\n\t" // 2+4+1+6=11 (T = 31) + "rjmp _head28" "\n\t" // 2 -> _head28 (next bit out) + "_seconddelay28:" "\n\t" // + "rcall _thirddelay28" "\n\t" // 2+4=6 saves 2 instruction words vs two rjmp .+0 + "_firstdelay28:" "\n\t" // first delay "nop" "\n\t" // 1 nop - "thirddelay28:" "\n\t" // third delay + "_thirddelay28:" "\n\t" // third delay "ret" "\n\t" // 4 - "nextbyte28:" "\n\t" // last bit of a byte (T = 21) + "_nextbyte28:" "\n\t" // last bit of a byte (T = 21) "st %a[port], %[lo]" "\n\t" // 1 PORT = lo (T = 22) "ldi %[bit] , 8" "\n\t" // 1 bit = 8 (T = 23) "ld %[byte] , %a[ptr]+" "\n\t" // 2 b = *ptr++ (T = 25) - "rcall thirddelay28" "\n\t" // 2+4 = 6 (T = 31) + "rcall _thirddelay28" "\n\t" // 2+4 = 6 (T = 31) "sbiw %[count], 1" "\n\t" // 2 i-- (T = 33) - "brne head28" "\n" // 2 if (i != 0) -> (next byte) () + "brne _head28" "\n" // 2 if (i != 0) -> _head28 (next byte) : [ptr] "+e" (ptr), [byte] "+r" (b), [bit] "+d" (bit), @@ -887,36 +900,35 @@ void tinyNeoPixel::show(void) { bit = 8; asm volatile( - "head32:" "\n\t" // Clk Pseudocode (T = 0) + "_head32:" "\n\t" // Clk Pseudocode (T = 0) "st %a[port], %[hi]" "\n\t" // 1 PORT = hi (T = 1) "sbrc %[byte], 7" "\n\t" // 1-2 if (b & 128) "mov %[next], %[hi]" "\n\t" // 0-1 next = hi (T = 3) "dec %[bit]" "\n\t" // 1 bit-- (T = 4) - "rcall zerothdelay32" "\n\t" // 2+4=6 + "rcall _zerothdelay32" "\n\t" // 2+4=6 "st %a[port], %[next]" "\n\t" // 1 PORT = next (T = 11) "mov %[next] , %[lo]" "\n\t" // 1 next = lo (T = 12) - "rcall firstdelay32" "\n\t" // 2+4+1+2 = 9 (T = 21) - "breq nextbyte32" "\n\t" // 1-2 if (bit == 0) (from dec above) + "rcall _firstdelay32" "\n\t" // 2+4+1+2 = 9 (T = 21) + "breq _nextbyte32" "\n\t" // 1-2 if (bit == 0) (from dec above) "rol %[byte]" "\n\t" // 1 b <<= 1 (T = 22) "st %a[port], %[lo]" "\n\t" // 1 PORT = lo (T = 23) - "rcall seconddelay32" "\n\t" // 2+4+6+3=15 (T = 38) - "rjmp head32" "\n\t" // 2 -> head20 (next bit out) - "seconddelay32:" "\n\t" // second delay 15 cycles - "rcall zerothdelay32" "\n\t" // 2+4=6 - "nop" "\n\t" // 1 nop - "firstdelay32:" "\n\t" // first delay 9 cycles - "nop" "\n\t" // 1 nop - "thirddelay32:" "\n\t" // third delay 8 cycles + "rcall _seconddelay32" "\n\t" // 2+4+6+3=15 (T = 38) + "rjmp _head32" "\n\t" // 2 -> _head32 (next bit out) + "_seconddelay32:" "\n\t" // second delay 15 cycles + "rcall _zerothdelay32" "\n\t" // 2+4=6 + "_firstdelay32:" "\n\t" // first delay 9 cycles "nop" "\n\t" // 1 nop - "zerothdelay32:" "\n\t" // zeroth delay 6 cycles + "_thirddelay32:" "\n\t" // third delay 8 cycles + "rjmp .+0" "\n\t" // 1 nop + "_zerothdelay32:" "\n\t" // zeroth delay 6 cycles "ret" "\n\t" // 4 - "nextbyte32:" "\n\t" // last bit of a byte (T = 23) + "_nextbyte32:" "\n\t" // last bit of a byte (T = 23) "st %a[port], %[lo]" "\n\t" // 1 PORT = lo (T = 24) "ldi %[bit] , 8" "\n\t" // 1 bit = 8 (T = 26) "ld %[byte] , %a[ptr]+" "\n\t" // 2 b = *ptr++ (T = 28) - "rcall thirddelay32" "\n\t" // 2+4+1+1 = 8 (T = 36) + "rcall _thirddelay32" "\n\t" // 2+4+2 = 8 (T = 36) "sbiw %[count], 1" "\n\t" // 2 i-- (T = 38) - "brne head32" "\n" // 2 if (i != 0) -> (next byte) () + "brne _head32" "\n" // 2 if (i != 0) -> _head32 (next byte) : [ptr] "+e" (ptr), [byte] "+r" (b), [bit] "+d" (bit), @@ -941,37 +953,37 @@ void tinyNeoPixel::show(void) { bit = 8; asm volatile( - "head36:" "\n\t" // Clk Pseudocode (T = 0) + "_head36:" "\n\t" // Clk Pseudocode (T = 0) "st %a[port], %[hi]" "\n\t" // 1 PORT = hi (T = 1) "sbrc %[byte], 7" "\n\t" // 1-2 if (b & 128) "mov %[next], %[hi]" "\n\t" // 0-1 next = hi (T = 3) "dec %[bit]" "\n\t" // 1 bit-- (T = 4) - "rcall zerothdelay36" "\n\t" // 2+4+2=8 + "rcall _zerothdelay36" "\n\t" // 2+4+2=8 "st %a[port], %[next]" "\n\t" // 1 PORT = next (T = 13) "mov %[next] , %[lo]" "\n\t" // 1 next = lo (T = 14) - "rcall firstdelay36" "\n\t" // 2+4+3 = 11 (T = 25) - "breq nextbyte36" "\n\t" // 1-2 if (bit == 0) (from dec above) + "rcall _firstdelay36" "\n\t" // 2+4+3 = 11 (T = 25) + "breq _nextbyte36" "\n\t" // 1-2 if (bit == 0) (from dec above) "rol %[byte]" "\n\t" // 1 b <<= 1 (T = 27) "st %a[port], %[lo]" "\n\t" // 1 PORT = lo (T = 28) - "rcall seconddelay36" "\n\t" // 2+4+3+2+2=15 (T = 43) - "rjmp head36" "\n\t" // 2 -> head20 (next bit out) - "seconddelay36:" "\n\t" // second delay 15 cycles + "rcall _seconddelay36" "\n\t" // 2+4+3+2+2=15 (T = 43) + "rjmp _head36" "\n\t" // 2 -> _head36 (next bit out) + "_seconddelay36:" "\n\t" // second delay 15 cycles "rjmp .+0" "\n\t" // 2 "rjmp .+0" "\n\t" // 2 - "firstdelay36:" "\n\t" // first delay 11 cycles + "_firstdelay36:" "\n\t" // first delay 11 cycles "nop" "\n\t" // 1 nop - "thirddelay36:" "\n\t" // third delay 10 cycles + "_thirddelay36:" "\n\t" // third delay 10 cycles "rjmp .+0" "\n\t" // 2 nop nop - "zerothdelay36:" "\n\t" // zeroth delay 8 cycles + "_zerothdelay36:" "\n\t" // zeroth delay 8 cycles "rjmp .+0" "\n\t" // 2 nop nop "ret" "\n\t" // 4 - "nextbyte36:" "\n\t" // last bit of a byte (T = 27) + "_nextbyte36:" "\n\t" // last bit of a byte (T = 27) "st %a[port], %[lo]" "\n\t" // 1 PORT = lo (T = 28) "ldi %[bit] , 8" "\n\t" // 1 bit = 8 (T = 29) "ld %[byte] , %a[ptr]+" "\n\t" // 2 b = *ptr++ (T = 31) - "rcall thirddelay36" "\n\t" // 2+4 = 10 (T = 41) + "rcall _thirddelay36" "\n\t" // 2+4 = 10 (T = 41) "sbiw %[count], 1" "\n\t" // 2 i-- (T = 43) - "brne head36" "\n" // 2 if (i != 0) -> (next byte) () + "brne _head36" "\n" // 2 if (i != 0) -> _head36 (next byte) : [ptr] "+e" (ptr), [byte] "+r" (b), [bit] "+d" (bit), @@ -997,38 +1009,37 @@ void tinyNeoPixel::show(void) { bit = 8; asm volatile( - "head40:" "\n\t" // Clk Pseudocode (T = 0) + "_head40:" "\n\t" // Clk Pseudocode (T = 0) "st %a[port], %[hi]" "\n\t" // 1 PORT = hi (T = 1) "sbrc %[byte], 7" "\n\t" // 1-2 if (b & 128) "mov %[next], %[hi]" "\n\t" // 0-1 next = hi (T = 3) "dec %[bit]" "\n\t" // 1 bit-- (T = 4) - "rcall zerothdelay40" "\n\t" // 2+4+4=10 + "rcall _zerothdelay40" "\n\t" // 2+4+4=10 "st %a[port], %[next]" "\n\t" // 1 PORT = next (T = 15) "mov %[next] , %[lo]" "\n\t" // 1 next = lo (T = 16) - "rcall firstdelay40" "\n\t" // 2+4+4+2 = 12 (T = 28) - "breq nextbyte40" "\n\t" // 1-2 if (bit == 0) (from dec above) + "rcall _firstdelay40" "\n\t" // 2+4+4+2 = 12 (T = 28) + "breq _nextbyte40" "\n\t" // 1-2 if (bit == 0) (from dec above) "rol %[byte]" "\n\t" // 1 b <<= 1 (T = 30) "st %a[port], %[lo]" "\n\t" // 1 PORT = lo (T = 31) - "rcall seconddelay40" "\n\t" // 2+4+3+2+3=17 (T = 48) - "rjmp head40" "\n\t" // 2 -> head20 (next bit out) - "seconddelay40:" "\n\t" // second delay 17 cycles + "rcall _seconddelay40" "\n\t" // 2+4+3+2+3=17 (T = 48) + "rjmp _head40" "\n\t" // 2 -> _head40 (next bit out) + "_seconddelay40:" "\n\t" // second delay 17 cycles "nop" "\n\t" // 1 nop "rjmp .+0" "\n\t" // 2 "rjmp .+0" "\n\t" // 2 - "thirddelay40:" "\n\t" // third delay 12 cycles - "firstdelay40:" "\n\t" // first delay 12 cycles + "_firstdelay40:" "\n\t" // first delay 12 cycles "rjmp .+0" "\n\t" // 2 nop nop - "zerothdelay40:" "\n\t" // zeroth delay 10 cycles + "_zerothdelay40:" "\n\t" // zeroth delay 10 cycles "rjmp .+0" "\n\t" // 2 nop nop "rjmp .+0" "\n\t" // 2 nop nop "ret" "\n\t" // 4 - "nextbyte40:" "\n\t" // last bit of a byte (T = 30) + "_nextbyte40:" "\n\t" // last bit of a byte (T = 30) "st %a[port], %[lo]" "\n\t" // 1 PORT = lo (T = 31) "ldi %[bit] , 8" "\n\t" // 1 bit = 8 (T = 32) "ld %[byte] , %a[ptr]+" "\n\t" // 2 b = *ptr++ (T = 34) - "rcall thirddelay40" "\n\t" // 2+4+4+2 = 12 (T = 46) + "rcall _firstdelay40" "\n\t" // 2+4+4+2 = 12 (T = 46) "sbiw %[count], 1" "\n\t" // 2 i-- (T = 48) - "brne head40" "\n" // 2 if (i != 0) -> (next byte) () + "brne _head40" "\n" // 2 if (i != 0) -> _head40 (next byte) : [ptr] "+e" (ptr), [byte] "+r" (b), [bit] "+d" (bit), @@ -1052,41 +1063,41 @@ void tinyNeoPixel::show(void) { next = lo; bit = 8; asm volatile( - "head48:" "\n\t" // Clk Pseudocode (T = 0) + "_head48:" "\n\t" // Clk Pseudocode (T = 0) "st %a[port], %[hi]" "\n\t" // 1 PORT = hi (T = 1) "sbrc %[byte], 7" "\n\t" // 1-2 if (b & 128) "mov %[next], %[hi]" "\n\t" // 0-1 next = hi (T = 3) "dec %[bit]" "\n\t" // 1 bit-- (T = 4) - "rcall zerothdelay48" "\n\t" // 2+4=13 + "rcall _zerothdelay48" "\n\t" // 2+4=13 "st %a[port], %[next]" "\n\t" // 1 PORT = next (T = 17) "mov %[next] , %[lo]" "\n\t" // 1 next = lo (T = 18) - "rcall firstdelay48" "\n\t" // 2+4+3 = 15 (T = 33) - "breq nextbyte48" "\n\t" // 1-2 if (bit == 0) (from dec above) + "rcall _firstdelay48" "\n\t" // 2+4+3 = 15 (T = 33) + "breq _nextbyte48" "\n\t" // 1-2 if (bit == 0) (from dec above) "rol %[byte]" "\n\t" // 1 b <<= 1 (T = 35) "st %a[port], %[lo]" "\n\t" // 1 PORT = lo (T = 36) - "rcall seconddelay48" "\n\t" // 2+4+3+2+3=22 (T = 58) - "rjmp head48" "\n\t" // 2 -> head20 (next bit out) - "seconddelay48:" "\n\t" // second delay 22 cycles + "rcall _seconddelay48" "\n\t" // 2+4+3+2+3=22 (T = 58) + "rjmp _head48" "\n\t" // 2 -> _head48 (next bit out) + "_seconddelay48:" "\n\t" // second delay 22 cycles "rjmp .+0" "\n\t" // 2 "rjmp .+0" "\n\t" // 2 "nop" "\n\t" // 1 nop - "thirddelay48:" "\n\t" // third delay 17 cycles + "_thirddelay48:" "\n\t" // third delay 17 cycles "rjmp .+0" "\n\t" // 2 - "firstdelay48:" "\n\t" // first delay 15 cycles + "_firstdelay48:" "\n\t" // first delay 15 cycles "rjmp .+0" "\n\t" // 2 nop nop - "zerothdelay48:" "\n\t" // zeroth delay 13 cycles + "_zerothdelay48:" "\n\t" // zeroth delay 13 cycles "nop" "\n\t" // 1 nop - "rcall emptydelay48" "\n\t" // 2+4 + "rcall _emptydelay48" "\n\t" // 2+4 "ret" "\n\t" // 4 - "emptydelay48:" "\n\t" // immediately returns: 2+4 = 6 cycles, for 2 words! + "_emptydelay48:" "\n\t" // immediately returns: 2+4 = 6 cycles, for 2 words! "ret" "\n\t" // 4 - "nextbyte48:" "\n\t" // last bit of a byte (T = 35) + "_nextbyte48:" "\n\t" // last bit of a byte (T = 35) "st %a[port], %[lo]" "\n\t" // 1 PORT = lo (T = 36) "ldi %[bit] , 8" "\n\t" // 1 bit = 8 (T = 37) "ld %[byte] , %a[ptr]+" "\n\t" // 2 b = *ptr++ (T = 39) - "rcall thirddelay48" "\n\t" // 2+4 = 17 (T = 56) + "rcall _thirddelay48" "\n\t" // 2+4 = 17 (T = 56) "sbiw %[count], 1" "\n\t" // 2 i-- (T = 58) - "brne head48" "\n" // 2 if (i != 0) -> (next byte) () + "brne _head48" "\n" // 2 if (i != 0) -> _head48 (next byte) : [ptr] "+e" (ptr), [byte] "+r" (b), [bit] "+d" (bit), @@ -1106,6 +1117,7 @@ void tinyNeoPixel::show(void) { #if (!defined(MILLIS_USE_TIMERNONE) && !defined(MILLIS_USE_TIMERRTC) && !defined(MILLIS_USE_TIMERRTC_XTAL) && !defined(MILLIS_USE_TIMERRTC_XOSC)) endTime = micros(); // Save EOD time for latch on next call + #pragma message("micros() present. This library assumes the canonical 50 us latch delay; some pixels will wait as long as 250us. In these cases, you must be sure to not call show more often. See documentation.") #else #pragma message("micros() is not available because millis is disabled from the tools subemnu. It is your responsibility to ensure a sufficient time has passed between calls to show(). See documentation.") #endif @@ -1443,3 +1455,4 @@ uint32_t tinyNeoPixel::gamma32(uint32_t x) { for(uint8_t i = 0; i<4; i++) y[i] = gamma8(y[i]); return x; // Packed 32-bit return } +// *INDENT-ON* diff --git a/megaavr/libraries/tinyNeoPixel/tinyNeoPixel.h b/megaavr/libraries/tinyNeoPixel/tinyNeoPixel.h index 96cbfd78..67f43481 100644 --- a/megaavr/libraries/tinyNeoPixel/tinyNeoPixel.h +++ b/megaavr/libraries/tinyNeoPixel/tinyNeoPixel.h @@ -209,6 +209,9 @@ class tinyNeoPixel { void begin(void), show(void), + #if (PROGMEM_SIZE > 4096UL) + show(uint16_t leds), + #endif setPin(uint8_t p), setPixelColor(uint16_t n, uint8_t r, uint8_t g, uint8_t b), setPixelColor(uint16_t n, uint8_t r, uint8_t g, uint8_t b, uint8_t w), diff --git a/megaavr/libraries/tinyNeoPixel_Static/README.md b/megaavr/libraries/tinyNeoPixel_Static/README.md index c7adb36b..aee1c43b 100644 --- a/megaavr/libraries/tinyNeoPixel_Static/README.md +++ b/megaavr/libraries/tinyNeoPixel_Static/README.md @@ -1,6 +1,6 @@ # tinyNeoPixel documentation is elsewhere tinyNeoPixel is a tweaked version of adafruitNeoPixel, supporting more clock speeds and correcting some improperly specified constraints, but should be code compatible excepting the change to the class name. This core also includes tinyNeoPixel_Static, which has been modified further to reduce the flash footprint. It is recommended to read the first one first, as it gives brief summaries of the API, before and discusses supported parts and particularly common issues relating to addressable LEDs, both specific to these parts and in general (including specific hazards that they can expose the user to if used improperly. -* [Summary and changes specific to tinyNeoPixel_Static](https://github.com/SpenceKonde/megaTinyCore/blob/master/megaavr/extras/tinyNeoPixel.md) - both normal and static versions use the same documentation. +* [Summary and changes specific to tinyNeoPixel_Static](https://github.com/SpenceKonde/DxCore/blob/master/megaavr/extras/tinyNeoPixel.md) - both normal and static versions use the same documentation. * [Full class reference from Adafruit](https://adafruit.github.io/Adafruit_NeoPixel/html/class_adafruit___neo_pixel.html) In the event that a function listed in that class reference is not defined in tinyNeoPixel, or in the event of differences in behavior between adafruitNeoPixel and tinyNeoPixel (except as noted in the first documentation link above), that is a bug, and is likely unknown to the maintainer of megaTinyCore - please report it via github issue (preferred) or by emailing SpenceKonde@gmail.com diff --git a/megaavr/libraries/tinyNeoPixel_Static/library.properties b/megaavr/libraries/tinyNeoPixel_Static/library.properties index f4335a8c..a436cc16 100644 --- a/megaavr/libraries/tinyNeoPixel_Static/library.properties +++ b/megaavr/libraries/tinyNeoPixel_Static/library.properties @@ -1,9 +1,9 @@ name=tinyNeoPixel Static -version=2.0.5 +version=2.0.7 author=Adafruit (modified by Spence Konde) maintainer=Spence Konde sentence=Arduino library for controlling single-wire-based LED pixels and strip for all modern (post 2016) AVR microcontrollers, and distributed with megaTinyCore and DxCore. -paragraph=This library is closely based on the original Adafruit_NeoPixel library. It has been modified to account for the improved ST performance on the tinyAVR 0/1/2-series, megaAVR 0-series, and AVR Dx/Ex-series, adding support for speeds from 4 MHz to 48 MHz (about the fastest you can get a Dx-series to run at, . No specific actions to choose the port the port at any speed (enabled by ST improvements). This version has been further modified: a statically defined buffer must be passed to the constructor, and the length cannot be changed; this eliminates the use of malloc/free (saving over 1k flash), and allows the compiler to report the memory used by the pixel buffer (which, with large numbers of LEDs, often consumes a large portion of available SRAM). You cannot change the length of the string at runtime as a result, however. You must set the pin output manually (but this permits you to use pinModeFast to save flash), and there is no need to call begin(). See the documentation for full description.
2.0.5 - correct some timing and compile issues at certain speeds.
2.0.4 - Add support for operation at lower speeds, possibly as low as 4 MHz, though these stretch the lengths of the low significantly. Ensure that the inline assembly is specified correctly.
2.0.3 - Fix issue when millis is disabled. +paragraph=This library is closely based on the original Adafruit_NeoPixel library. It has been modified to account for the improved ST performance on the tinyAVR 0-series, tinyAVR 1-series and megaAVR 0-series, and add support for speeds from 4 MHz to 48 MHz. No specific actions to choose the port the port at any speed (enabled by ST improvements). Please refer to the documentation for more information.
2.0.7 - Fix critical defect in 10 and 12 MHz implementations which would output the first bit only.
2.0.6 - correct naming of labels in asm to conform with our naming policy. Add show(number), which will show up to the first (number) leds. This allows the static allocation version to light up a varying number of LEDs. 2.0.5 - correct some timing and compile issues at certain speeds.
2.0.4 - Add support for operation at speeds as low as 4 MHz. Ensure that the inline assembly is specified correctly.
2.0.3 - Fix issue when millis is disabled. category=Display url=https://github.com/SpenceKonde/megaTinyCore/blob/master/megaavr/extras/tinyNeoPixel.md architectures=megaavr diff --git a/megaavr/libraries/tinyNeoPixel_Static/tinyNeoPixel_Static.cpp b/megaavr/libraries/tinyNeoPixel_Static/tinyNeoPixel_Static.cpp index 9a004578..98e87edf 100644 --- a/megaavr/libraries/tinyNeoPixel_Static/tinyNeoPixel_Static.cpp +++ b/megaavr/libraries/tinyNeoPixel_Static/tinyNeoPixel_Static.cpp @@ -74,12 +74,27 @@ void tinyNeoPixel::updateLatch(uint16_t us) { latchTime = (us < 6 ? 6 : us); // there are no devices in production with a shorter latch waiting time, and thus is the shortest reasonable latch delay. } // *INDENT-OFF* astyle don't like assembly -void tinyNeoPixel::show(void) { +#if (PROGMEM_SIZE > 4096UL) +void tinyNeoPixel::show(void) { + show(0); +} +void tinyNeoPixel::show(uint16_t leds) { + volatile uint16_t i = numBytes;; + if (!(numLEDs <= leds || !leds)) { + if (wOffset == rOffset) { + i = leds + leds + leds; + } else { + i = leds << 2; + } + } if ((!pixels) || pin >= NUM_DIGITAL_PINS) { return; } - +#else + void tinyNeoPixel::show(void) { + volatile uint16_t i = numBytes; // Loop counter +#endif // Data latch = 50+ microsecond pause in the output stream. Rather than // put a delay at the end of the function, the ending time is noted and // the function will simply hold off (if needed) on issuing the @@ -117,7 +132,7 @@ void tinyNeoPixel::show(void) { // So, having timing that's not strictly speaking correct located in // anywhere that won't make a 0 look like a 1 or the other way around, // you can kind of get away with a lot. - + // // This code manages to avoid having having to do that all the way down // to 4-5 MHz one. which takes 1400 us to run for 6 bits and 1600 for the // last two. @@ -146,8 +161,7 @@ void tinyNeoPixel::show(void) { // run at those speeds, only that - if they do - you can control WS2812s // with them. - volatile uint16_t - i = numBytes; // Loop counter + volatile uint8_t *ptr = pixels, // Pointer to next byte b = *ptr++, // Current byte value @@ -519,9 +533,10 @@ void tinyNeoPixel::show(void) { "rcall _bitTime10" "\n\t" // Bit 1 // Bit 0: "st %a[port], %[hi]" "\n\t" // 1 PORT = hi (T = 1) - "rjmp .+0" "\n\t" // 2 nop nop (T = 3) - "ld %[byte] , %a[ptr]+" "\n\t" // 2 b = *ptr++ (T = 5) - "st %a[port], %[next]" "\n\t" // 1 PORT = next (T = 6) + "nop" "\n\t" // 1 nop (T = 2) + "ld %[byte] , %a[ptr]+" "\n\t" // 2 b = *ptr++ (T = 4) + "st %a[port], %[next]" "\n\t" // 1 PORT = next (T = 5) + "nop" "\n\t" // 1 nop (T = 6) "mov %[next] , %[lo]" "\n\t" // 1 next = lo (T = 7) "sbrc %[byte] , 7" "\n\t" // 1-2 if (b & 0x80) (T = 8) "mov %[next] , %[hi]" "\n\t" // 0-1 next = hi (T = 9) @@ -532,10 +547,11 @@ void tinyNeoPixel::show(void) { "_bitTime10:" "\n\t" // nop nop nop (T = 4) "st %a[port], %[next]" "\n\t" // 1 PORT = next (T = 5) "mov %[next], %[lo]" "\n\t" // 1 next = lo (T = 6) - "rol %[byte]" "\n\t" // 1 b <<= 1 (T = 7) + "lsl %[byte]" "\n\t" // 1 b <<= 1 (T = 7) "sbrc %[byte], 7" "\n\t" // 1-2 if (b & 0x80) (T = 8) "mov %[next], %[hi]" "\n\t" // 0-1 next = hi (T = 9) "st %a[port], %[lo]" "\n\t" // 1 PORT = lo (T = 10) + "ret" "\n\t" // 4 return to above where we called from "_done10:" "\n" : [ptr] "+e" (ptr), [byte] "+r" (b), @@ -586,13 +602,13 @@ void tinyNeoPixel::show(void) { "rcall _bitTime12" "\n\t" // Bit 1 // Bit 0: "st %a[port], %[hi]" "\n\t" // 1 PORT = hi (T = 1) - "rjmp .+0" "\n\t" // 2 nop nop (T = 3) - "ld %[byte] , %a[ptr]+" "\n\t" // 2 b = *ptr++ (T = 5) - "st %a[port], %[next]" "\n\t" // 1 PORT = next (T = 6) - "mov %[next] , %[lo]" "\n\t" // 1 next = lo (T = 7) - "sbrc %[byte] , 7" "\n\t" // 1-2 if (b & 0x80) (T = 8) - "mov %[next] , %[hi]" "\n\t" // 0-1 next = hi (T = 9) - "nop" "\n\t" // 1 (T = 10) + "nop" "\n\t" // 1 nop (T = 2) + "ld %[byte] , %a[ptr]+" "\n\t" // 2 b = *ptr++ (T = 4) + "st %a[port], %[next]" "\n\t" // 1 PORT = next (T = 5) + "mov %[next] , %[lo]" "\n\t" // 1 next = lo (T = 6) + "sbrc %[byte] , 7" "\n\t" // 1-2 if (b & 0x80) (T = 7) + "mov %[next] , %[hi]" "\n\t" // 0-1 next = hi (T = 8) + "rjmp .+0" "\n\t" // 1 nop nop (T = 10) "st %a[port], %[lo]" "\n\t" // 1 PORT = lo (T = 11) "sbiw %[count], 1" "\n\t" // 2 i-- (T = 13) "brne _head12" "\n\t" // 2 if (i != 0) -> (next byte) @@ -600,11 +616,12 @@ void tinyNeoPixel::show(void) { "_bitTime12:" "\n\t" // nop nop nop (T = 4) "st %a[port], %[next]" "\n\t" // 1 PORT = next (T = 5) "mov %[next], %[lo]" "\n\t" // 1 next = lo (T = 6) - "rol %[byte]" "\n\t" // 1 b <<= 1 (T = 7) + "lsl %[byte]" "\n\t" // 1 b <<= 1 (T = 7) "sbrc %[byte], 7" "\n\t" // 1-2 if (b & 0x80) (T = 8) "mov %[next], %[hi]" "\n\t" // 0-1 next = hi (T = 9) "nop" "\n\t" // 1 (T = 10) "st %a[port], %[lo]" "\n\t" // 1 PORT = lo (T = 11) + "ret" "\n\t" // 4 return to above where we called from "_done12:" "\n" : [ptr] "+e" (ptr), [byte] "+r" (b), @@ -632,7 +649,7 @@ void tinyNeoPixel::show(void) { bit = 8; asm volatile( - "_head20:" "\n\t" // Clk Pseudocode (T = 0) + "_head16:" "\n\t" // Clk Pseudocode (T = 0) "st %a[port], %[hi]" "\n\t" // 1 PORT = hi (T = 1) "nop" "\n\t" // 1 nop (T = 2) "sbrc %[byte], 7" "\n\t" // 1-2 if (b & 128) @@ -641,21 +658,21 @@ void tinyNeoPixel::show(void) { "st %a[port], %[next]" "\n\t" // 1 PORT = next (T = 6) "nop" "\n\t" // 1 nop (T = 7) "mov %[next] , %[lo]" "\n\t" // 1 next = lo (T = 8) - "breq _nextbyte20" "\n\t" // 1-2 if (bit == 0) (from dec above) + "breq _nextbyte16" "\n\t" // 1-2 if (bit == 0) (from dec above) "rol %[byte]" "\n\t" // 1 b <<= 1 (T = 10) "rjmp .+0" "\n\t" // 2 nop nop (T = 12) "nop" "\n\t" // 1 nop (T = 13) "st %a[port], %[lo]" "\n\t" // 1 PORT = lo (T = 14) "rjmp .+0" "\n\t" // 2 nop nop (T = 16) "rjmp .+0" "\n\t" // 2 nop nop (T = 18) - "rjmp _head20" "\n\t" // 2 -> _head20 (next bit out) (T=20) - "_nextbyte20:" "\n\t" // (T = 10) + "rjmp _head16" "\n\t" // 2 -> _head16 (next bit out) (T=20) + "_nextbyte16:" "\n\t" // (T = 10) "ldi %[bit] , 8" "\n\t" // 1 bit = 8 (T = 11) "ld %[byte] , %a[ptr]+" "\n\t" // 2 b = *ptr++ (T = 13) "st %a[port], %[lo]" "\n\t" // 1 PORT = lo (T = 14) "rjmp .+0" "\n\t" // 2 nop nop (T = 16) "sbiw %[count], 1" "\n\t" // 2 i-- (T = 18) - "brne _head20" "\n" // 2 if (i != 0) -> (next byte) (T=20) + "brne _head16" "\n" // 2 if (i != 0) -> _head16 (bit 0 of next byte.) (T=20) : [ptr] "+e" (ptr), [byte] "+r" (b), [bit] "+d" (bit), @@ -689,13 +706,13 @@ void tinyNeoPixel::show(void) { "rjmp .+0" "\n\t" // 2 nop nop (T = 7) "st %a[port], %[next]" "\n\t" // 1 PORT = next (T = 8) "mov %[next] , %[lo]" "\n\t" // 1 next = lo (T = 9) - "breq _nextbyte20" "\n\t" // 1-2 if (bit == 0) (from dec above) + "breq _nextbyte20" "\n\t" // 1-2 if (bit == 0) (from dec above) -> _nextbyte20 "rol %[byte]" "\n\t" // 1 b <<= 1 (T = 11) "rjmp .+0" "\n\t" // 2 nop nop (T = 13) "nop" "\n\t" // 1 nop (T = 14) "st %a[port], %[lo]" "\n\t" // 1 PORT = lo (T = 15) "rcall _onlydelay20" "\n\t" // 2+4+2 = 7 (T = 23) - "rjmp _head20" "\n\t" // 2 -> head20 (next bit out T = 25) + "rjmp _head20" "\n\t" // 2 -> _head20 (next bit out T = 25) "_onlydelay20:" "\n\t" // 2 rcall (T = n+2) "rjmp .+0" "\n\t" // 2 nop nop (T = n+4) "ret" "\n\t" // 4 4x nop (T = n+8) @@ -708,7 +725,7 @@ void tinyNeoPixel::show(void) { "rjmp .+0" "\n\t" // 2 nop nop (T = 19) "rjmp .+0" "\n\t" // 2 nop nop (T = 21) "sbiw %[count], 1" "\n\t" // 2 i-- (T = 23) - "brne _head20" "\n" // 2 if (i != 0) -> (next byte) () + "brne _head20" "\n" // 2 if (i != 0) -> _head20 (next byte) : [ptr] "+e" (ptr), [byte] "+r" (b), [bit] "+d" (bit), @@ -744,11 +761,11 @@ void tinyNeoPixel::show(void) { "st %a[port], %[next]" "\n\t" // 1 PORT = next (T = 9) "mov %[next] , %[lo]" "\n\t" // 1 next = lo (T = 10) "rjmp .+0" "\n\t" // 2 nop nop (T = 14) - "breq _nextbyte24" "\n\t" // 1-2 if (bit == 0) (from dec above) + "breq _nextbyte24" "\n\t" // 1-2 if (bit == 0) (from dec above) -> _nextbyte24 "rol %[byte]" "\n\t" // 1 b <<= 1 (T = 16) "st %a[port], %[lo]" "\n\t" // 1 PORT = lo (T = 17) "rcall _seconddelay24" "\n\t" // 2+4+5=11 (T = 28) - "rjmp _head24" "\n\t" // 2 -> head24 (next bit out) + "rjmp _head24" "\n\t" // 2 -> _head24 (next bit out) "_seconddelay24:" "\n\t" // "rjmp .+0" "\n\t" // 2 "rjmp .+0" "\n\t" // 2 @@ -761,7 +778,7 @@ void tinyNeoPixel::show(void) { "ld %[byte] , %a[ptr]+" "\n\t" // 2 b = *ptr++ (T = 19) "rcall _thirddelay24" "\n\t" // 2+4+1=7 (T = 26) "sbiw %[count], 1" "\n\t" // 2 i-- (T = 28) - "brne _head24" "\n" // 2 if (i != 0) -> (next byte) (T = 30) + "brne _head24" "\n" // 2 if (i != 0) -> _head24 (next byte) (T = 30) : [ptr] "+e" (ptr), [byte] "+r" (b), [bit] "+d" (bit), @@ -799,11 +816,11 @@ void tinyNeoPixel::show(void) { "st %a[port], %[next]" "\n\t" // 1 PORT = next (T = 9) "mov %[next] , %[lo]" "\n\t" // 1 next = lo (T = 10) "rcall _firstdelay28" "\n\t" // 2+4 = 7 (T = 17) - "breq _nextbyte28" "\n\t" // 1-2 if (bit == 0) (from dec above) + "breq _nextbyte28" "\n\t" // 1-2 if (bit == 0) (from dec above) -> _nextbyte28 "rol %[byte]" "\n\t" // 1 b <<= 1 (T = 19) "st %a[port], %[lo]" "\n\t" // 1 PORT = lo (T = 20) "rcall _seconddelay28" "\n\t" // 2+4+1+6=11 (T = 31) - "rjmp _head28" "\n\t" // 2 -> head20 (next bit out) + "rjmp _head28" "\n\t" // 2 -> _head28 (next bit out) "_seconddelay28:" "\n\t" // "rcall _thirddelay28" "\n\t" // 2+4=6 saves 2 instruction words vs two rjmp .+0 "_firstdelay28:" "\n\t" // first delay @@ -816,7 +833,7 @@ void tinyNeoPixel::show(void) { "ld %[byte] , %a[ptr]+" "\n\t" // 2 b = *ptr++ (T = 25) "rcall _thirddelay28" "\n\t" // 2+4 = 6 (T = 31) "sbiw %[count], 1" "\n\t" // 2 i-- (T = 33) - "brne _head28" "\n" // 2 if (i != 0) -> (next byte) () + "brne _head28" "\n" // 2 if (i != 0) -> _head28 (next byte) : [ptr] "+e" (ptr), [byte] "+r" (b), [bit] "+d" (bit), @@ -855,23 +872,22 @@ void tinyNeoPixel::show(void) { "rol %[byte]" "\n\t" // 1 b <<= 1 (T = 22) "st %a[port], %[lo]" "\n\t" // 1 PORT = lo (T = 23) "rcall _seconddelay32" "\n\t" // 2+4+6+3=15 (T = 38) - "rjmp _head32" "\n\t" // 2 -> head20 (next bit out) + "rjmp _head32" "\n\t" // 2 -> _head32 (next bit out) "_seconddelay32:" "\n\t" // second delay 15 cycles "rcall _zerothdelay32" "\n\t" // 2+4=6 - "nop" "\n\t" // 1 nop "_firstdelay32:" "\n\t" // first delay 9 cycles "nop" "\n\t" // 1 nop "_thirddelay32:" "\n\t" // third delay 8 cycles - "nop" "\n\t" // 1 nop + "rjmp .+0" "\n\t" // 1 nop "_zerothdelay32:" "\n\t" // zeroth delay 6 cycles "ret" "\n\t" // 4 "_nextbyte32:" "\n\t" // last bit of a byte (T = 23) "st %a[port], %[lo]" "\n\t" // 1 PORT = lo (T = 24) "ldi %[bit] , 8" "\n\t" // 1 bit = 8 (T = 26) "ld %[byte] , %a[ptr]+" "\n\t" // 2 b = *ptr++ (T = 28) - "rcall _thirddelay32" "\n\t" // 2+4+1+1 = 8 (T = 36) + "rcall _thirddelay32" "\n\t" // 2+4+2 = 8 (T = 36) "sbiw %[count], 1" "\n\t" // 2 i-- (T = 38) - "brne _head32" "\n" // 2 if (i != 0) -> (next byte) () + "brne _head32" "\n" // 2 if (i != 0) -> _head32 (next byte) : [ptr] "+e" (ptr), [byte] "+r" (b), [bit] "+d" (bit), @@ -909,7 +925,7 @@ void tinyNeoPixel::show(void) { "rol %[byte]" "\n\t" // 1 b <<= 1 (T = 27) "st %a[port], %[lo]" "\n\t" // 1 PORT = lo (T = 28) "rcall _seconddelay36" "\n\t" // 2+4+3+2+2=15 (T = 43) - "rjmp _head36" "\n\t" // 2 -> head20 (next bit out) + "rjmp _head36" "\n\t" // 2 -> _head36 (next bit out) "_seconddelay36:" "\n\t" // second delay 15 cycles "rjmp .+0" "\n\t" // 2 "rjmp .+0" "\n\t" // 2 @@ -926,7 +942,7 @@ void tinyNeoPixel::show(void) { "ld %[byte] , %a[ptr]+" "\n\t" // 2 b = *ptr++ (T = 31) "rcall _thirddelay36" "\n\t" // 2+4 = 10 (T = 41) "sbiw %[count], 1" "\n\t" // 2 i-- (T = 43) - "brne _head36" "\n" // 2 if (i != 0) -> (next byte) () + "brne _head36" "\n" // 2 if (i != 0) -> _head36 (next byte) : [ptr] "+e" (ptr), [byte] "+r" (b), [bit] "+d" (bit), @@ -965,12 +981,11 @@ void tinyNeoPixel::show(void) { "rol %[byte]" "\n\t" // 1 b <<= 1 (T = 30) "st %a[port], %[lo]" "\n\t" // 1 PORT = lo (T = 31) "rcall _seconddelay40" "\n\t" // 2+4+3+2+3=17 (T = 48) - "rjmp head40" "\n\t" // 2 -> head20 (next bit out) + "rjmp _head40" "\n\t" // 2 -> _head40 (next bit out) "_seconddelay40:" "\n\t" // second delay 17 cycles "nop" "\n\t" // 1 nop "rjmp .+0" "\n\t" // 2 "rjmp .+0" "\n\t" // 2 - "_thirddelay40:" "\n\t" // third delay 12 cycles "_firstdelay40:" "\n\t" // first delay 12 cycles "rjmp .+0" "\n\t" // 2 nop nop "_zerothdelay40:" "\n\t" // zeroth delay 10 cycles @@ -981,9 +996,9 @@ void tinyNeoPixel::show(void) { "st %a[port], %[lo]" "\n\t" // 1 PORT = lo (T = 31) "ldi %[bit] , 8" "\n\t" // 1 bit = 8 (T = 32) "ld %[byte] , %a[ptr]+" "\n\t" // 2 b = *ptr++ (T = 34) - "rcall _thirddelay40" "\n\t" // 2+4+4+2 = 12 (T = 46) + "rcall _firstdelay40" "\n\t" // 2+4+4+2 = 12 (T = 46) "sbiw %[count], 1" "\n\t" // 2 i-- (T = 48) - "brne _head40" "\n" // 2 if (i != 0) -> (next byte) () + "brne _head40" "\n" // 2 if (i != 0) -> _head40 (next byte) : [ptr] "+e" (ptr), [byte] "+r" (b), [bit] "+d" (bit), @@ -1020,7 +1035,7 @@ void tinyNeoPixel::show(void) { "rol %[byte]" "\n\t" // 1 b <<= 1 (T = 35) "st %a[port], %[lo]" "\n\t" // 1 PORT = lo (T = 36) "rcall _seconddelay48" "\n\t" // 2+4+3+2+3=22 (T = 58) - "rjmp _head48" "\n\t" // 2 -> head20 (next bit out) + "rjmp _head48" "\n\t" // 2 -> _head48 (next bit out) "_seconddelay48:" "\n\t" // second delay 22 cycles "rjmp .+0" "\n\t" // 2 "rjmp .+0" "\n\t" // 2 @@ -1041,7 +1056,7 @@ void tinyNeoPixel::show(void) { "ld %[byte] , %a[ptr]+" "\n\t" // 2 b = *ptr++ (T = 39) "rcall _thirddelay48" "\n\t" // 2+4 = 17 (T = 56) "sbiw %[count], 1" "\n\t" // 2 i-- (T = 58) - "brne _head48" "\n" // 2 if (i != 0) -> (next byte) () + "brne _head48" "\n" // 2 if (i != 0) -> _head48 (next byte) : [ptr] "+e" (ptr), [byte] "+r" (b), [bit] "+d" (bit), @@ -1063,7 +1078,7 @@ void tinyNeoPixel::show(void) { // Save EOD time for latch on next call #pragma message("micros() present. This library assumes the canonical 50 us latch delay; some pixels will wait as long as 250us. In these cases, you must be sure to not call show more often. See documentation.") #else - #pragma "micros() is not available because millis is disabled from the tools subemnu. It is your responsibility to ensure a sufficient time has passed between calls to show(). See documentation." + #pragma message("micros() is not available because millis is disabled from the tools subemnu. It is your responsibility to ensure a sufficient time has passed between calls to show(). See documentation.") #endif } @@ -1354,7 +1369,7 @@ void tinyNeoPixel::setBrightness(uint8_t b) { oldBrightness = brightness - 1; // De-wrap old brightness value uint16_t scale; if (oldBrightness == 0) { - scale = 0; // Avoid /0 + scale = 0; // Avoid 0 } else if (b == 255) { scale = 65535 / oldBrightness; } else { diff --git a/megaavr/libraries/tinyNeoPixel_Static/tinyNeoPixel_Static.h b/megaavr/libraries/tinyNeoPixel_Static/tinyNeoPixel_Static.h index 7c3a0458..8f22724e 100644 --- a/megaavr/libraries/tinyNeoPixel_Static/tinyNeoPixel_Static.h +++ b/megaavr/libraries/tinyNeoPixel_Static/tinyNeoPixel_Static.h @@ -195,6 +195,9 @@ class tinyNeoPixel { void show(void), + #if (PROGMEM_SIZE > 4096UL) + show(uint16_t leds), + #endif setPin(uint8_t p), setPixelColor(uint16_t n, uint8_t r, uint8_t g, uint8_t b), setPixelColor(uint16_t n, uint8_t r, uint8_t g, uint8_t b, uint8_t w),