Problem with two wire build between 11.3.1 and 5.4.1

sbfreddie

Well-known member
All:
This problem is a doozy.
The problem started when I rebuilt an already working project that was originally built in Teensy 1.57, where everything was working fine.
When I built this project without making any changes it refused to startup, meaning blank screen on the TFT and no output to the serial monitor. Fortunately I saved a copy of the original project folder and was able to reload the .hex file with the Teensy loader program. When it started up it revealed this error from the crash report on the serial monitor:
Code:
CrashReport:
  A problem occurred at (system time) 17:14:44
  Code was executing from address 0x6000177E
  CFSR: 82
.(DACCVIOL) Data Access Violation
.(MMARVALID) Accessed Address: 0x0 (nullptr)
.  Check code at 0x6000177E - very likely a bug!
.  Run "addr2line -e mysketch.ino.elf 0x6000177E" for filename & line number.
  Temperature inside the chip was 35.00 °C
  Startup CPU clock speed is 600MHz

So I converted the .elf file to a .lst file with objdump and checked out address 0x6000177E which contains this obj code:
Code:
60001768 <TwoWire::begin()>:
60001768:	4916      	ldr	r1, [pc, #88]	; (600017c4 <TwoWire::begin()+0x5c>)
6000176a:	6942      	ldr	r2, [r0, #20]
6000176c:	6b8b      	ldr	r3, [r1, #56]	; 0x38
6000176e:	f423 037c 	bic.w	r3, r3, #16515072	; 0xfc0000
60001772:	f443 2380 	orr.w	r3, r3, #262144	; 0x40000
60001776:	b570      	push	{r4, r5, r6, lr}
60001778:	638b      	str	r3, [r1, #56]	; 0x38
6000177a:	4604      	mov	r4, r0
6000177c:	4d12      	ldr	r5, [pc, #72]	; (600017c8 <TwoWire::begin()+0x60>)
6000177e:	e9d2 1300 	ldrd	r1, r3, [r2]
60001782:	680a      	ldr	r2, [r1, #0]
60001784:	431a      	orrs	r2, r3
60001786:	6903      	ldr	r3, [r0, #16]
60001788:	600a      	str	r2, [r1, #0]
6000178a:	2202      	movs	r2, #2
6000178c:	2100      	movs	r1, #0
6000178e:	611a      	str	r2, [r3, #16]
60001790:	4a0e      	ldr	r2, [pc, #56]	; (600017cc <TwoWire::begin()+0x64>)
60001792:	6119      	str	r1, [r3, #16]
60001794:	649a      	str	r2, [r3, #72]	; 0x48
60001796:	2201      	movs	r2, #1
60001798:	625a      	str	r2, [r3, #36]	; 0x24
6000179a:	629d      	str	r5, [r3, #40]	; 0x28
6000179c:	f44f 3530 	mov.w	r5, #180224	; 0x2c000
600017a0:	62dd      	str	r5, [r3, #44]	; 0x2c
600017a2:	f04f 1501 	mov.w	r5, #65537	; 0x10001
600017a6:	6c9e      	ldr	r6, [r3, #72]	; 0x48
600017a8:	651e      	str	r6, [r3, #80]	; 0x50
600017aa:	6219      	str	r1, [r3, #32]
600017ac:	659d      	str	r5, [r3, #88]	; 0x58
600017ae:	611a      	str	r2, [r3, #16]
600017b0:	7e01      	ldrb	r1, [r0, #24]
600017b2:	f7ff ff4f 	bl	60001654 <TwoWire::configSDApin(unsigned char)>
600017b6:	7e61      	ldrb	r1, [r4, #25]
600017b8:	4620      	mov	r0, r4
600017ba:	e8bd 4070 	ldmia.w	sp!, {r4, r5, r6, lr}
600017be:	f7ff bf8d 	b.w	600016dc <TwoWire::configSCLpin(unsigned char)>
600017c2:	bf00      	nop
600017c4:	400fc000 	.word	0x400fc000
600017c8:	05050bb8 	.word	0x05050bb8
600017cc:	1928373b 	.word	0x1928373b

Finding something as common as TwoWire::begin to blow up like this is very strange, so I looked closer at the libWire.a files after compiling and discovered that they were built with different compilers. The working version looks this:
Code:
!<arch>
/               0           0     0     0       786       `
ÊÊÊÊÊÊÊÊÊÊÊÊÊÊÊÊÊÊÊÊÊÊÊÊÊÊÊÊÊÊÊ_ZN5Print17availableForWriteEv_ZN7TwoWire9availableEv_ZN7TwoWire4readEv_ZN7TwoWire4peekEv_ZN7TwoWire5flushEv_ZN7TwoWire5writeEh_ZN7TwoWire5writeEPKhj_ZN7TwoWire5beginEv_ZN7TwoWire3endEv_ZN7TwoWire11force_clockEv_ZN7TwoWire9wait_idleEv_ZN7TwoWire15endTransmissionEh_ZN7TwoWire11requestFromEhhh_ZN7TwoWire11requestFromEhhmhh_ZN7TwoWire5beginEh_ZN7TwoWire3isrEv_Z10lpi2c1_isrv_Z10lpi2c3_isrv_Z10lpi2c4_isrv_ZN7TwoWire6setSDAEh_ZN7TwoWire12configSDApinEh_ZN7TwoWire6setSCLEh_ZN7TwoWire12configSCLpinEh_ZN7TwoWire8setClockEm_ZTV7TwoWireWire2Wire1_ZN7TwoWire13i2c4_hardwareE_ZN7TwoWire13i2c3_hardwareEWire_ZN7TwoWire13i2c1_hardwareE//                                              20        `
WireKinetis.cpp.o/

Wire.cpp.o/     1677591497  502   20    100644  772       `
ELF(ú4(	GCC: (GNU Tools for ARM Embedded Processors) 5.4.1 20160919 (release) [ARM/embedded-5-branch revision 240496]A6aeabi,Cortex-M7
M	
"ÒˇWire.cpp.symtab.strtab.shstrtab.text.data.bss.comment.ARM.attributes4!4'4,04o5p£7VE‹p	L
WireIMXRT.cpp.o/1677591498  502   20    100644  9712      `
ELF(X4(74

The non-working one looks like this:
Code:
!<arch>
/               0           0     0     0       900       `
!_ZN5Print17availableForWriteEv_ZN7TwoWire9availableEv_ZN7TwoWire4readEv_ZN7TwoWire4peekEv_ZN7TwoWire5flushEv_ZN7TwoWire5writeEh_ZN7TwoWire5writeEPKhj_ZN7TwoWireC2EP13IMXRT_LPI2C_tRKNS_14I2C_Hardware_tE_ZN7TwoWireC1EP13IMXRT_LPI2C_tRKNS_14I2C_Hardware_tE_ZN7TwoWire3endEv_ZN7TwoWire11force_clockEv_ZN7TwoWire9wait_idleEv_ZN7TwoWire15endTransmissionEh_ZN7TwoWire11requestFromEhhh_ZN7TwoWire11requestFromEhhmhh_ZN7TwoWire3isrEv_Z10lpi2c1_isrv_Z10lpi2c3_isrv_Z10lpi2c4_isrv_ZN7TwoWire12configSDApinEh_ZN7TwoWire6setSDAEh_ZN7TwoWire12configSCLpinEh_ZN7TwoWire5beginEh_ZN7TwoWire6setSCLEh_ZN7TwoWire5beginEv_ZN7TwoWire8setClockEm_ZTV7TwoWireWire2Wire1_ZN7TwoWire13i2c4_hardwareE_ZN7TwoWire13i2c3_hardwareEWire_ZN7TwoWire13i2c1_hardwareE//                                              20        `
WireKinetis.cpp.o/

Wire.cpp.o/     1688752734  502   20    100644  708       `
ELF(\4(	GCC: (Arm GNU Toolchain 11.3.Rel1) 11.3.1 20220712A1aeabi'7E-M
M	
"ÒˇWire.cpp.symtab.strtab.shstrtab.text.data.bss.comment.ARM.attributes4!4'4,0445ph2úp	
EWireIMXRT.cpp.o/1688752735  502   20    100644  9500      `
ELF(‰4(;:

Both of these projects were built with PlatformIO. Here is the build output from the non-working version:
Code:
 *  Executing task: platformio run 

Processing release (platform: teensy; board: teensy41; framework: arduino)
---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
Verbose mode can be enabled via `-v, --verbose` option
Build number: 1.0.109 (waiting for upload before next increment)
CONFIGURATION: https://docs.platformio.org/page/boards/teensy/teensy41.html
PLATFORM: Teensy (4.18.0) > Teensy 4.1
HARDWARE: IMXRT1062 600MHz, 512KB RAM, 7.75MB Flash
DEBUG: Current (jlink) External (jlink)
PACKAGES: 
 - framework-arduinoteensy @ 1.158.0 (1.58) 
 - tool-teensy @ 1.158.0 (1.58) 
 - toolchain-gccarmnoneeabi-teensy @ 1.110301.0 (11.3.1)
LDF: Library Dependency Finder -> https://bit.ly/configure-pio-ldf
LDF Modes: Finder ~ chain, Compatibility ~ soft
Found 106 compatible libraries
Scanning dependencies...
Dependency Graph
|-- Eds_BME680
|-- TMP117-Arduino @ 1.0.3
|-- WDT_T4 @ 0.1
|-- AT24CX
|-- DS3231RTC
|-- EEPROMEx @ 0.0.0-alpha+sha.09d7586108
|-- SPI @ 1.0
|-- SparkFun_u-blox_GNSS_Arduino_Library
|-- Streaming @ 6.0.9
|-- Timezone @ 1.2.4
|-- Wire @ 1.0
|-- Ra8876LiteTeensy
|-- MCP9808
|-- memcpy
Building in release mode
Compiling .pio/build/release/src/TestingTFT.cpp.o
Compiling .pio/build/release/libe9f/SPI/SPI.cpp.o
Compiling .pio/build/release/libb68/Wire/Wire.cpp.o
Compiling .pio/build/release/libb68/Wire/WireIMXRT.cpp.o
Compiling .pio/build/release/libb68/Wire/WireKinetis.cpp.o
Compiling .pio/build/release/libb68/Wire/utility/twi.c.o
Compiling .pio/build/release/lib525/Eds_BME680/Eds_BME680.cpp.o
Compiling .pio/build/release/lib313/TMP117/TMP117.cpp.o
Compiling .pio/build/release/lib9cd/AT24CX/AT24CX.cpp.o
Compiling .pio/build/release/lib983/Time/DateStrings.cpp.o
Compiling .pio/build/release/lib983/Time/Time.cpp.o
src/TestingTFT.cpp: In function 'void setup()':
src/TestingTFT.cpp:2083:12: warning: variable 'temp_t' set but not used [-Wunused-but-set-variable]
 2083 |     time_t temp_t;
      |            ^~~~~~
Archiving .pio/build/release/lib313/libTMP117.a
Archiving .pio/build/release/lib9cd/libAT24CX.a
Indexing .pio/build/release/lib313/libTMP117.a
Indexing .pio/build/release/lib9cd/libAT24CX.a
Archiving .pio/build/release/libb68/libWire.a
Compiling .pio/build/release/lib2c0/DS3231RTC/DS3231RTC.cpp.o
Indexing .pio/build/release/libb68/libWire.a
Compiling .pio/build/release/lib9d8/EEPROMEx/EEPROMex.cpp.o
Compiling .pio/build/release/lib61f/SparkFun_u-blox_GNSS_Arduino_Library/SparkFun_u-blox_GNSS_Arduino_Library.cpp.o
Compiling .pio/build/release/liba32/Timezone/Timezone.cpp.o
Archiving .pio/build/release/libe9f/libSPI.a
Indexing .pio/build/release/libe9f/libSPI.a
Compiling .pio/build/release/libc61/Ra8876LiteTeensy/RA8876_t3.cpp.o
Archiving .pio/build/release/lib983/libTime.a
Indexing .pio/build/release/lib983/libTime.a
Compiling .pio/build/release/libc61/Ra8876LiteTeensy/_font_ComicSansMS.c.o
Archiving .pio/build/release/lib525/libEds_BME680.a
Indexing .pio/build/release/lib525/libEds_BME680.a
Compiling .pio/build/release/libc61/Ra8876LiteTeensy/font_Arial.c.o
Archiving .pio/build/release/lib9d8/libEEPROMEx.a
Indexing .pio/build/release/lib9d8/libEEPROMEx.a
Archiving .pio/build/release/liba32/libTimezone.a
Compiling .pio/build/release/libc61/Ra8876LiteTeensy/glcdfont.c.o
Indexing .pio/build/release/liba32/libTimezone.a
Compiling .pio/build/release/libfc6/MCP9808/mcp9808.cpp.o
Compiling .pio/build/release/libe93/memcpy/memcpy.c.o
Compiling .pio/build/release/FrameworkArduino/AudioStream.cpp.o
Archiving .pio/build/release/lib2c0/libDS3231RTC.a
Indexing .pio/build/release/lib2c0/libDS3231RTC.a
Compiling .pio/build/release/FrameworkArduino/CrashReport.cpp.o
Compiling .pio/build/release/FrameworkArduino/DMAChannel.cpp.o
Compiling .pio/build/release/FrameworkArduino/EventResponder.cpp.o
Archiving .pio/build/release/libe93/libmemcpy.a
Indexing .pio/build/release/libe93/libmemcpy.a
Compiling .pio/build/release/FrameworkArduino/HardwareSerial.cpp.o
Compiling .pio/build/release/FrameworkArduino/HardwareSerial1.cpp.o
Archiving .pio/build/release/libfc6/libMCP9808.a
Indexing .pio/build/release/libfc6/libMCP9808.a
Compiling .pio/build/release/FrameworkArduino/HardwareSerial2.cpp.o
Compiling .pio/build/release/FrameworkArduino/HardwareSerial3.cpp.o
Compiling .pio/build/release/FrameworkArduino/HardwareSerial4.cpp.o
Compiling .pio/build/release/FrameworkArduino/HardwareSerial5.cpp.o
Compiling .pio/build/release/FrameworkArduino/HardwareSerial6.cpp.o
Compiling .pio/build/release/FrameworkArduino/HardwareSerial7.cpp.o
Compiling .pio/build/release/FrameworkArduino/HardwareSerial8.cpp.o
Compiling .pio/build/release/FrameworkArduino/IPAddress.cpp.o
Compiling .pio/build/release/FrameworkArduino/IntervalTimer.cpp.o
Compiling .pio/build/release/FrameworkArduino/Print.cpp.o
Compiling .pio/build/release/FrameworkArduino/Stream.cpp.o
Compiling .pio/build/release/FrameworkArduino/Time.cpp.o
Compiling .pio/build/release/FrameworkArduino/Tone.cpp.o
Compiling .pio/build/release/FrameworkArduino/WMath.cpp.o
Compiling .pio/build/release/FrameworkArduino/WString.cpp.o
Compiling .pio/build/release/FrameworkArduino/analog.c.o
Compiling .pio/build/release/FrameworkArduino/bootdata.c.o
Compiling .pio/build/release/FrameworkArduino/clockspeed.c.o
Compiling .pio/build/release/FrameworkArduino/debugprintf.c.o
Compiling .pio/build/release/FrameworkArduino/delay.c.o
Compiling .pio/build/release/FrameworkArduino/digital.c.o
Compiling .pio/build/release/FrameworkArduino/eeprom.c.o
Compiling .pio/build/release/FrameworkArduino/extmem.c.o
Compiling .pio/build/release/FrameworkArduino/fuse.c.o
Compiling .pio/build/release/FrameworkArduino/interrupt.c.o
Compiling .pio/build/release/FrameworkArduino/keylayouts.c.o
Compiling .pio/build/release/FrameworkArduino/main.cpp.o
Compiling .pio/build/release/FrameworkArduino/memcpy-armv7m.S.o
Compiling .pio/build/release/FrameworkArduino/memset.S.o
Compiling .pio/build/release/FrameworkArduino/new.cpp.o
Compiling .pio/build/release/FrameworkArduino/nonstd.c.o
Compiling .pio/build/release/FrameworkArduino/pwm.c.o
Compiling .pio/build/release/FrameworkArduino/rtc.c.o
Compiling .pio/build/release/FrameworkArduino/serialEvent.cpp.o
Compiling .pio/build/release/FrameworkArduino/serialEvent1.cpp.o
Compiling .pio/build/release/FrameworkArduino/serialEvent2.cpp.o
Compiling .pio/build/release/FrameworkArduino/serialEvent3.cpp.o
Compiling .pio/build/release/FrameworkArduino/serialEvent4.cpp.o
Compiling .pio/build/release/FrameworkArduino/serialEvent5.cpp.o
Compiling .pio/build/release/FrameworkArduino/serialEvent6.cpp.o
Compiling .pio/build/release/FrameworkArduino/serialEvent7.cpp.o
Compiling .pio/build/release/FrameworkArduino/serialEvent8.cpp.o
Compiling .pio/build/release/FrameworkArduino/serialEventUSB1.cpp.o
Compiling .pio/build/release/FrameworkArduino/serialEventUSB2.cpp.o
Compiling .pio/build/release/FrameworkArduino/sm_alloc_valid.c.o
Compiling .pio/build/release/FrameworkArduino/sm_calloc.c.o
Compiling .pio/build/release/FrameworkArduino/sm_free.c.o
Compiling .pio/build/release/FrameworkArduino/sm_hash.c.o
Compiling .pio/build/release/FrameworkArduino/sm_malloc.c.o
Compiling .pio/build/release/FrameworkArduino/sm_malloc_stats.c.o
Compiling .pio/build/release/FrameworkArduino/sm_pool.c.o
Compiling .pio/build/release/FrameworkArduino/sm_realloc.c.o
Compiling .pio/build/release/FrameworkArduino/sm_realloc_i.c.o
Compiling .pio/build/release/FrameworkArduino/sm_realloc_move.c.o
Compiling .pio/build/release/FrameworkArduino/sm_szalloc.c.o
Compiling .pio/build/release/FrameworkArduino/sm_util.c.o
Compiling .pio/build/release/FrameworkArduino/sm_zalloc.c.o
Compiling .pio/build/release/FrameworkArduino/startup.c.o
Compiling .pio/build/release/FrameworkArduino/tempmon.c.o
Compiling .pio/build/release/FrameworkArduino/usb.c.o
Compiling .pio/build/release/FrameworkArduino/usb_audio.cpp.o
Compiling .pio/build/release/FrameworkArduino/usb_desc.c.o
Compiling .pio/build/release/FrameworkArduino/usb_flightsim.cpp.o
Compiling .pio/build/release/FrameworkArduino/usb_inst.cpp.o
Compiling .pio/build/release/FrameworkArduino/usb_joystick.c.o
Compiling .pio/build/release/FrameworkArduino/usb_keyboard.c.o
Compiling .pio/build/release/FrameworkArduino/usb_midi.c.o
Compiling .pio/build/release/FrameworkArduino/usb_mouse.c.o
Compiling .pio/build/release/FrameworkArduino/usb_mtp.c.o
Compiling .pio/build/release/FrameworkArduino/usb_rawhid.c.o
Compiling .pio/build/release/FrameworkArduino/usb_seremu.c.o
Compiling .pio/build/release/FrameworkArduino/usb_serial.c.o
Compiling .pio/build/release/FrameworkArduino/usb_serial2.c.o
Compiling .pio/build/release/FrameworkArduino/usb_serial3.c.o
Compiling .pio/build/release/FrameworkArduino/usb_touch.c.o
Compiling .pio/build/release/FrameworkArduino/yield.cpp.o
Archiving .pio/build/release/libFrameworkArduino.a
Indexing .pio/build/release/libFrameworkArduino.a
Archiving .pio/build/release/libc61/libRa8876LiteTeensy.a
Indexing .pio/build/release/libc61/libRa8876LiteTeensy.a
Archiving .pio/build/release/lib61f/libSparkFun_u-blox_GNSS_Arduino_Library.a
Indexing .pio/build/release/lib61f/libSparkFun_u-blox_GNSS_Arduino_Library.a
Linking .pio/build/release/firmware.elf
Memory region         Used Size  Region Size  %age Used
            ITCM:      119820 B       512 KB     22.85%
            DTCM:       37120 B       512 KB      7.08%
             RAM:       12416 B       512 KB      2.37%
           FLASH:        162 KB      7936 KB      2.04%
            ERAM:          0 GB        16 MB      0.00%
Calculating size .pio/build/release/firmware.elf
Checking size .pio/build/release/firmware.elf
teensy_size: Memory Usage on Teensy 4.1:
teensy_size:   FLASH: code:125676, data:31976, headers:8232   free for files:7960580
teensy_size:    RAM1: variables:37120, code:119816, padding:11256   free for local variables:356096
teensy_size:    RAM2: variables:12416  free for malloc/new:511872
Building .pio/build/release/firmware.hex

Any one have any ideas about how to fix this problem??

Thanks,
Ed
 
I’ve seen strange crashes before when building with PlatformIO when changing between different Teensyduino versions. Try deleting ~/.platformio/packages/framework-arduinoteensy* and ~/.platformio/packages/toolchain-gccarmnoneeabi*, and then rebuilding.
(Wildcard because there may be more than one version.)
 
Shawn:
I already did that or something similar, I uninstalled PlatformIO in VSCode, then I discarded the entire .platformio folder from my user folder, then reinstalled PlatformIO in VSCode and it completely redown-loaded the .platformio folder. Did not make a difference.
Still get the same problem.

Thanks,
Ed
 
Last edited:
All:
I have been experimenting with this problem more.
I converted this project to an Arduino sketch which confirms the problem, it works with Teensy 1.57.2 but not with Teensy 1.58 or Teensy 1.59 using Arduino 2.1.1.
Here is the sketch for Arduino:

View attachment Sensors_Test.zip
Please try this to see if any of you can point me in the correct direction.

Thanks,
Ed
 
Can you trim this to a small program which demonstrates the problem (using Arduino IDE, not PlatformIO)?

Sensors_Test.zip contains 118 files having a total of 83,312 lines.
 
Fairly small ... 75 lines, but lots of diagnostics built in!
Rich (BB code):
// Need to make TwoWire private members public
// to allow inspection of .hardware and .port
#include <Wire.h>

class WireTest
{
    TwoWire& myWire;
    bool begun;
  public:
    WireTest(TwoWire& _myWire)
      : myWire(_myWire), begun(false)
      {
        Serial.println("WireTest::WireTest()");
        begin();
      }
     
    void begin(void)
    {
      bool doCheck = false; // change this to disable / enable crash
     
      Serial.println((uint32_t) &myWire.hardware,HEX);
      Serial.println((uint32_t) myWire.port,HEX);

      if (doCheck && 0 == (uint32_t) myWire.port)
        Serial.println("NULL port - begin() not called");
      else
      {
        if (begun)
          Serial.println("port OK but begin() already called");
        else
        {
          Serial.printf("port %sOK - begin() called\n",
                        (0 == (uint32_t) myWire.port)
                            ?"not "
                            :"");
          delay(10); // let Serial complete before we crash...
          myWire.begin(); // ...which we do here!
          begun = true;
        }
      }
    }
 
};

WireTest myWire(Wire);

void setup()
{
  Serial.println("================\nSetup");

  myWire.begin();
}

void loop()
{
  Serial.println("================\nLoop");
  myWire.begin();
  delay(500);
}

extern "C" {
  void startup_late_hook(void)
  {
    while (!Serial)
      ;
    Serial.println("================\nLate hook");
    if (CrashReport)
    {
      Serial.print(CrashReport);
    }
    Serial.println((uint32_t) &Wire.hardware,HEX);
    Serial.println((uint32_t) Wire.port,HEX);
    Serial.println("================\nConstructors");
  }
}
tested with Arduino 1.8.19, TeensyDuino 1.59-beta2
 
Last edited:
Maybe I'm not seeing the problem and talking obvious stuff, but this code relys on the global object "Wire" being constructed before the global object "myWire" because "myWire"s constructor takes a reference to Wire. AFAIK there is no guarantee on the construction order of global objects, so Wire might not be constructed yet when it is passed to the constructor of myWire. (Edit: see JMarshs post #5)

Instead of passing a reference to a TowWire object in the constructor I'd pass its address in the begin function. Then it works as expected. But, as mentioned, I might not see the actual issue...

Code:
#include <Wire.h>

class WireTest
{
    TwoWire* myWire;
    bool begun;

 public:
    WireTest(/*TwoWire& _myWire*/)
        : /*myWire(_myWire),*/ begun(false)
    {
        Serial.println("WireTest::WireTest()");
        // begin();
    }

    void begin(TwoWire* _myWire)
    {
        myWire       = _myWire;
        bool doCheck = false; // change this to disable / enable crash

        Serial.println((uint32_t)&myWire->hardware, HEX);
        Serial.println((uint32_t)myWire->port, HEX);

        if (doCheck && 0 == (uint32_t)myWire->port)
            Serial.println("NULL port - begin() not called");
        else
        {
            if (begun)
                Serial.println("port OK but begin() already called");
            else
            {
                Serial.printf("port %sOK - begin() called\n",
                              (0 == (uint32_t)myWire->port)
                                  ? "not "
                                  : "");
                delay(10);       // let Serial complete before we crash...
                myWire->begin(); // ...which we do here!
                begun = true;
            }
        }
    }
};

WireTest myWire;

void setup()
{
    Serial.println("================\nSetup");

    myWire.begin(&Wire);
}

void loop()
{
    Serial.println("================\nLoop");
    myWire.begin(&Wire);
    delay(500);
}

extern "C" {
void startup_late_hook(void)
{
    while (!Serial)
        ;
    Serial.println("================\nLate hook");
    if (CrashReport)
    {
        Serial.print(CrashReport);
    }
    Serial.println((uint32_t)&Wire.hardware, HEX);
    Serial.println((uint32_t)Wire.port, HEX);
    Serial.println("================\nConstructors");
}
}
 
You’re right, up to a point, but in theory Paul went to some effort to make the Wire constructor constexpr, so TwoWire::begin() should work from within my WireTest class’s constructor. However, this appears not to be working with Teensyduino 1.59. As noted on the linked thread, the same was true for SPI objects, which definitely started causing problems (e.g. within some Audio library objects), and Paul managed to fix - we’re hoping he can work his magic again!
 
Indeed the intention was for these static instance (Wire, Wire1, Wire2) to be fully static compile-time initialized. Even though using Wire or any other classes from the constructor of a static instance of any other class is definitely not recommended, the idea behind fully static compile-time initialization was to allow this not recommended design (use of Serial, Wire, SPI in other constructors) to "just work".

With gcc 5.4 and C++14 dialect (in Teensyduino 1.57 and earlier), using constexpr in the constructor achieved that goal. We lost this fully static compile time initialization with the upgrade to gcc 11.3 in Teensyduino 1.58. It's simply taken some time for programs depending on this to become noticed.

This is on my list for 1.59.
 
Indeed the intention was for these static instance (Wire, Wire1, Wire2) to be fully static compile-time initialized. Even though using Wire or any other classes from the constructor of a static instance of any other class is definitely not recommended, the idea behind fully static compile-time initialization was to allow this not recommended design (use of Serial, Wire, SPI in other constructors) to "just work".

Understood. I didn't find much online on the conditions when a compiler is able (or willing) to do a compile time construction so I did some experiments to clarify this. Here my test program:

Code:
class test_t
{
 public:
    test_t(){}
    int someVariable = 42;
};

test_t test;

// check if test was compiletime constructed
extern "C" {
void startup_late_hook(void)
{
    while (!Serial) {}
    Serial.printf("%s (someVariable=%d)\n", test.someVariable == 0 ? "Dynamic" : "Compiletime", test.someVariable);
}

void setup(){}
void loop(){}
}

As expected, test is constructed dynamically after startup_late_hook(). Consequently it prints:
Code:
Dynamic (someVariable=0)

Declaring the constructor as constexpr
Code:
class test_t
{
 public:
    [B][COLOR="#0000CD"]constexpr[/COLOR] [/B]test_t(){}
    int someVariable = 42;
};
convinces the compiler to construct test statically and it prints
Code:
Compiletime (someVariable=42)

However, declaring the constructor constexpr is not sufficient, the compiler needs to be able to generate a constexpr object in principle (you don't have to actually declare the object constexpr but it must be possible to do so).
Let's pass a parameter to the class, e.g. an integer to demonstrate this:
Code:
class test_t
{
 public:
    constexpr test_t(int i){}
    int someVariable = 42;
};

int i = 17;
test_t test(i);

This time the compiler refuses to construct it at compile time. However, if we make it obvious that the passed in parameter is constant the compiler does what we want:
Code:
[B][COLOR="#0000CD"]const [/COLOR][/B]int i = 17;
test_t test(i);
or
Code:
test_t test(17);
All those cases lead to static construction.

As a simple check, we can try if we could in principle generate a constexpr object from the type by declaring one. Whenever this compiles, the compiler is able to construct it at compile time.
Examples:
Code:
constexpr test_t t1(6); // compiles and constructs at compile time

const int i = 7;
constexpr test_t t2(i); // compiles and constructs at compile time

int j = 5;
constexpr test_t t3(j); // doesn't compile, removing the constexpr would make it compile but the compiler would not construct it statically

With this method the compiler also nicely tells us what the problem is:
Code:
src/main.cpp:17:22: error: the value of 'j' is not usable in a constant expression
   17 | constexpr test_t t3(j); // doesn't compile
      |                      ^
src/main.cpp:16:5: note: 'int j' is not const
   16 | int j = 5;
      |     ^
make.exe: *** [makefile:231: .vsteensy/build/src/main.cpp.o] Error 1

===============

Now try this for the orignial issue with Wire. I.e., try to declare a constexpr Wire:

WireIMXRT.cpp lines 408ff
Code:
PROGMEM
constexpr TwoWire::I2C_Hardware_t TwoWire::i2c1_hardware = {
	CCM_CCGR2, CCM_CCGR2_LPI2C1(CCM_CCGR_ON),
		{{18, 3 | 0x10, &IOMUXC_LPI2C1_SDA_SELECT_INPUT, 1}, {0xff, 0xff, nullptr, 0}},
		{{19, 3 | 0x10, &IOMUXC_LPI2C1_SCL_SELECT_INPUT, 1}, {0xff, 0xff, nullptr, 0}},
	IRQ_LPI2C1, &lpi2c1_isr
};
TwoWire Wire(&IMXRT_LPI2C1, TwoWire::i2c1_hardware);
[B][COLOR="#0000FF"]constexpr TwoWire WireTest(&IMXRT_LPI2C1, TwoWire::i2c1_hardware);[/COLOR][/B]

Indeed the compiler complains:
Code:
lib/Wire//WireIMXRT.cpp:416:28: error: 'reinterpret_cast' from integer to pointer
  416 | constexpr TwoWire WireTest(&IMXRT_LPI2C1, TwoWire::i2c1_hardware);       
make.exe: *** [makefile:214: .vsteensy/build/lib/Wire//WireIMXRT.cpp.o] Error 1

And this clearly shows the issue. As @Shawn recently explained in an other thread a cast from an integral type to a pointer is never considered const in c++. (Don't ask me why, there are a couple of 'over my head discussions' on Stackoverflow about this)
Anyway, generating a constexpr object of type TwoWire is not possible due to the non constant parameter. Thus, the compiler refuses to construct it at compile time.

Fixing it is not difficult. The usual way to work around the "integral to pointer cast restriction" is to store uintptr_t type variables instead of the pointers and cast them to a pointer whenever you use them. I did this to the TwoWire class and it fixes the issue.

@Paul: If you want to implement that solution I can do a PR but I currently have no time to do some extensive testing.

Edit: Here my version: https://github.com/luni64/Wire (compiles and constructs statically, but no functional tests yet)
 
Last edited:
I can't help but feel templates (which require resolution to happen at compile time) would be better than relying on constexpr.
 
Would templates be compatible with libraries which take a pointer or reference allow configuring which I2C port they use?
 
@Paul: If you want to implement that solution I can do a PR but I currently have no time to do some extensive testing.

Yes, looks good. Please send a pull request.

I was going to do this over the weekend using the style in the SPI library. But this way looks fine too.


As @Shawn recently explained in an other thread a cast from an integral type to a pointer is never considered const in c++. (Don't ask me why, there are a couple of 'over my head discussions' on Stackoverflow about this)

I also don't understand why this is. But we just have to deal with the fact that gcc 5.4 treated it as const but gcc 11.3 doesn't. Have I mentioned why I'm so reluctant to update the toolchain frequently?
 
I can't help but feel templates (which require resolution to happen at compile time) would be better than relying on constexpr.

Might work as well. What do you want to use as template parameter?


Would templates be compatible with libraries which take a pointer or reference allow configuring which I2C port they use?

Instead of
Code:
TwoWire Wire3(IMXRT_LPI2C2_ADDRESS, TwoWire::i2c2_hardware);

You would have something like

Code:
using Wire2_t = TwoWire<IMXRT_LPI2C4_ADDRESS, TwoWire::i2c4_hardware>;
using Wire3_t = TwoWire<IMXRT_LPI2C2_ADDRESS, TwoWire::i2c2_hardware>;

Wire2_t Wire2;
Wire3_t Wire3;
etc.

Of course, using a templated version would generate completely different types for each port including duplicated code. Besides the code bloat you'd need to have a common base class if you want to pass Wire objects of different type (port) to functions or other classes. I'm not sure if all this is worth the effort?
 
Last edited:
I also don't understand why this is. But we just have to deal with the fact that gcc 5.4 treated it as const but gcc 11.3 doesn't.

I think I do: Constructs like
Code:
constexpr IMXRT_LPI2C_t* x =  (*(IMXRT_LPI2C_t *)0x403FC000)
were never legal c++ code but gcc silently ignored the errror. From the first teensy versions (gcc 4.?) to the current version, gcc got more and more compliant to the standard. I remember having to fix code related to this on each compiler change.
So, nothing changed with constepxr constructors, but the new gcc (rightfully) regards e.g. (*(IMXRT_LPI2C_t *)0x403FC000) not as const anymore which leads to the issue.
 
Last edited:
@Paul: If you want to implement that solution I can do a PR but I currently have no time to do some extensive testing.

Edit: Here my version: https://github.com/luni64/Wire (compiles and constructs statically, but no functional tests yet)
Brief testing, works for me, including calling TwoWire::begin() from within the constructor of a class I cobbled together to simplify using the M5Stack I²C encoder / pot units. On a side note, it didn't work initially ... until I remembered my PR for the Wire class isn't pulled in yet! Combine the two and everything is fine, but as I say only a brief test.
 
I’m fairly certain that if the `Wire` object is created without `constexpr`, even though the constructor is marked as `constexpr`, there’s still no guarantee that it’s compile-time constructed. Eg.:
Code:
TwoWire Wire(parameters);

In other words, if this does get compile-time constructed then that’s still relying on compiler-specific behaviour.

If all the conditions are met, then this statement will get compile-time constructed:
Code:
[B]constexpr[/B] TwoWire Wire(parameters);

In other words, I'm disagreeing with this:
Declaring the constructor as constexpr
Code:
class test_t
{
 public:
    [B][COLOR="#0000CD"]constexpr[/COLOR] [/B]test_t(){}
    int someVariable = 42;
};
convinces the compiler to construct test statically and it prints
Code:
Compiletime (someVariable=42)

However, declaring the constructor constexpr is not sufficient, the compiler needs to be able to generate a constexpr object in

There's no guarantee that this will happen — yes, you did say it's not sufficient — unless the object definition is also `constexpr` (you didn't state this was one of the necessary conditions for the guarantee). It might convince the compiler to do compile-time construction. If it does, great! But the problem might be pushed to a later compiler version if it chooses not to.

(Corrections and disagreements welcome. The more I learn about C++, less I know; so many subtleties...)
 
Last edited:
@shawn I don't know (and I don't believe) if c++14/17 defines under which conditions the compiler is supposed to do compile-time constructions. So, I fully agree with you that the next compiler might change this behaviour. However, and AFAIK, gcc was quite consistent with this over the last versions. Chances are good, that it will work in the future as well.
As I mentioned, one of the things which changed between versions is that gcc increased its standard compliance regarding the (non)constness of casts from intergral to pointer types which seems to be the root cause of the observed issue.

For me the main hypothesis, derived from my experiments is, that gcc will implement compile-time construction if (not exclusive)
1) One of the class constructors is declared constexpr (not necessarily the one used)
2) The object to be constructed from the class could be declared as constexpr (this requires all the things you mentioned in the other thread)
2.1) You do not actually need to declare the object as constexpr, but declaring it constexpr needs to compile.

So far I didn't find an example which contradicts this hypothesis. Just for the fun of it I'll check tomorrow if this also holds for other compiler versions and c++ standards.

Again, I do believe that this is compiler dependent behaviour. On the other hand, this is not about writing code for each and every compiler and standard version. The compiler, the used c++ standard and the core versions are strongly coupled in any case.
 
I fixed const init with HardwareSerial. Also added friend functions to Wire, SPI, HardwareSerial for future testing without needing to edit the classes for public access. So far this is my work-in-progress test code (Teensy 4.x only). Might someday turn this into something more elaborate. Maybe. But realistically so many other important issues are pending and we're probably going to stay on gcc 11.3 and C++17 for a least a couple years.

Code:
#include <Wire.h>
#include <SPI.h>

uintptr_t Teensyduino_Test_constinit_Wire(int instance, int index) {
  if (instance == 0) {
    if (index == -1) return (uintptr_t)("Wire");
    if (index == 0) return 2;
    if (index == 1) return Wire.portAddr;
    if (index == 2) return (uintptr_t)&Wire.hardware;
  }
  if (instance == 1) {
    if (index == -1) return (uintptr_t)("Wire1");
    if (index == 0) return 2;
    if (index == 1) return Wire1.portAddr;
    if (index == 2) return (uintptr_t)&Wire2.hardware;
  }
  if (instance == 2) {
    if (index == -1) return (uintptr_t)("Wire2");
    if (index == 0) return 2;
    if (index == 1) return Wire2.portAddr;
    if (index == 2) return (uintptr_t)&Wire2.hardware;
  }
#if defined(ARDUINO_TEENSY_MICROMOD)
  if (instance == 3) {
    if (index == -1) return (uintptr_t)("Wire3");
    if (index == 0) return 2;
    if (index == 1) return Wire3.portAddr;
    if (index == 2) return (uintptr_t)&Wire3.hardware;
  }
#endif
  return 0;
}


uintptr_t Teensyduino_Test_constinit_SPI(int instance, int index) {
  if (instance == 0) {
    if (index == -1) return (uintptr_t)("SPI");
    if (index == 0) return 2;
    if (index == 1) return SPI.port_addr;
    if (index == 2) return SPI.hardware_addr;
  }
  if (instance == 1) {
    if (index == -1) return (uintptr_t)("SPI1");
    if (index == 0) return 2;
    if (index == 1) return SPI1.port_addr;
    if (index == 2) return SPI1.hardware_addr;
  }
  if (instance == 2) {
    if (index == -1) return (uintptr_t)("SPI2");
    if (index == 0) return 2;
    if (index == 1) return SPI2.port_addr;
    if (index == 2) return SPI2.hardware_addr;
  }
  return 0;
}

uintptr_t Teensyduino_Test_constinit_HardwareSerial(int instance, int index) {
  if (instance == 0) {
    if (index == -1) return (uintptr_t)("Serial");
    if (index == 0) return 2;
    if (index == 1) return Serial1.port_addr;
    if (index == 2) return (uintptr_t)Serial1.hardware;
  }
  if (instance == 1) {
    if (index == -1) return (uintptr_t)("Serial2");
    if (index == 0) return 2;
    if (index == 1) return Serial2.port_addr;
    if (index == 2) return (uintptr_t)Serial2.hardware;
  }
  if (instance == 2) {
    if (index == -1) return (uintptr_t)("Serial3");
    if (index == 0) return 2;
    if (index == 1) return Serial3.port_addr;
    if (index == 2) return (uintptr_t)Serial3.hardware;
  }
  if (instance == 3) {
    if (index == -1) return (uintptr_t)("Serial4");
    if (index == 0) return 2;
    if (index == 1) return Serial4.port_addr;
    if (index == 2) return (uintptr_t)Serial4.hardware;
  }
  if (instance == 4) {
    if (index == -1) return (uintptr_t)("Serial5");
    if (index == 0) return 2;
    if (index == 1) return Serial5.port_addr;
    if (index == 2) return (uintptr_t)Serial5.hardware;
  }
  if (instance == 5) {
    if (index == -1) return (uintptr_t)("Serial6");
    if (index == 0) return 2;
    if (index == 1) return Serial6.port_addr;
    if (index == 2) return (uintptr_t)Serial6.hardware;
  }
  if (instance == 6) {
    if (index == -1) return (uintptr_t)("Serial7");
    if (index == 0) return 2;
    if (index == 1) return Serial7.port_addr;
    if (index == 2) return (uintptr_t)Serial7.hardware;
  }
#if defined(ARDUINO_TEENSY41)
  if (instance == 7) {
    if (index == -1) return (uintptr_t)("Serial8");
    if (index == 0) return 2;
    if (index == 1) return Serial8.port_addr;
    if (index == 2) return (uintptr_t)Serial8.hardware;
  }
#endif
  return 0;
}



void print_info(uintptr_t (*f)(int, int)) {

  int instance = 0;
  while (1) {
    const char *name = (char *)f(instance, -1);
    if (!name) break;
    int count = f(instance, 0);
    for (int i = 1; i <= count; i++) {
      uintptr_t n = f(instance, i);
      Serial.print(name);
      Serial.print(": var #");
      Serial.print(i);
      Serial.print(" = ");
      Serial.println(n, HEX);
    }
    instance++;
  }
}


extern "C" {
  void startup_late_hook() {
    while (!Serial) ; // wait for serial monitor
    delay(15);
    Serial.println("startup_late_hook");
    print_info(Teensyduino_Test_constinit_Wire);
    print_info(Teensyduino_Test_constinit_SPI);
    print_info(Teensyduino_Test_constinit_HardwareSerial);
  }
}

void setup() {
  Serial.println("\nsetup");
  print_info(Teensyduino_Test_constinit_Wire);
  print_info(Teensyduino_Test_constinit_SPI);
  print_info(Teensyduino_Test_constinit_HardwareSerial);
}

void loop() {
  delay(1);
}
 
Back
Top