T4 SPI port register - how to reset it?

Status
Not open for further replies.

Rezo

Well-known member
I'm working on a project that requires CAN bus and SPI based display and I am currently using FlexCAN_t4 and HX8357_t3 which work perfectly together.

Recently I introduced Snooze into the project in order to put the entire setup into hibernate mode as I don't have a means of cutting power.
I chose hibernate mode as it has the lowest power consumption and it does a "hard" reset when it wakes up and reinitializes all the libraries and configuration which is what I need.

I straight away ran into an issue, where the T4 would go to sleep but would not wake up (wake up is driven via a digital pin going high).
With help from KurtE I was able to fix that by adding the following code to the end function in the SPI library:
Before:
Code:
void SPIClass::end(){}
}

After:
Code:
void SPIClass::end(){
// only do something if we have begun
	if (hardware().clock_gate_register & hardware().clock_gate_mask) {
		port().CR = 0;  // turn off the enable
		//pinMode(hardware().miso_pin[miso_pin_index], INPUT_DISABLE);
		//pinMode(hardware().mosi_pin[mosi_pin_index], INPUT_DISABLE);
		//pinMode(hardware().sck_pin[sck_pin_index], INPUT_DISABLE);
	}
}
After the change to SPI.end the T4 goes to sleep and wakes up from the digital pin going high.

But nowI have a 2nd issue, and I've been able to pin point it to the SPI.transfer function.
As soon as the code hits the transfer function it gets stuck there. I've reached out to duff regarding this and he will try look into it in the next week, but he suspects that the SPI register is not resetting when the T4 wakes up and therefore might be causing the issue.

I'm no expert when it comes to debugging SPI or even knowing the in and outs of it, but I figured I'd try and get a head start on this and help with gathering info to address this issue.

So as the title states, does anyone know how to check the SPI port register and reset it on a T4?
Attached is the test sketch I am using where you can replicate the issue upon wakeup from hibernate
 

Attachments

  • Snooze_SPI_issue.ino
    1.3 KB · Views: 65
Yep, tested it before posting this thread. Also added it to the HX library and got the same output in the serial monitor.
What do the functions writecommand_cont and writedata8_contin the library do? I do see them tinkering with some SPI stuff, and that's where it's sticking, just like with the plain SPI test sketch I uploaded
 
Sorry, it has been a nice day outside today so I am sort of snoozing ;)

And I never have used Snooze. So far my low power mode has been to turn the power off :D So you may have to give a clue on what this sketch is supposed to do...

Code:
#include "SPI.h"
#include <Snooze.h>
SnoozeDigital digital;
SnoozeUSBSerial usb;
SnoozeSPI spi;
SnoozeBlock config_teensy40(usb, digital);


void setup(){
  while (!Serial && millis() < 5000);
    Serial.begin(115200);
    Serial.print("Going into Setup \n");
        
    SPI.begin();
    Serial.print("SPI.begin \n");
    delay(5);
    SPI.beginTransaction(SPISettings(2000000, MSBFIRST, SPI_MODE0));
    Serial.print("SPI.beginTransaction \n");
    SPI.endTransaction();
    Serial.print("SPI.endTransaction \n");
    digitalWrite(10, LOW);
    Serial.print("digital.Write \n");
    SPI.transfer(0x88);
    Serial.print("SPI transfer 0x88 \n");
    SPI.transfer(0x03);
    Serial.print("SPI transfer 0x03 \n");
    digitalWrite(10, HIGH);
    Serial.print("digital.Write \n");
    Serial.print("SPI init \n");

    
    digital.pinMode(22, INPUT, RISING);//pin, mode, type
    Serial.print("Snooze digital.pinMode init \n");
}


const long SleepTimer = 30000; // Time to go to sleep
unsigned long startTime = 0;
void loop(){
  int who;
  if (millis() > startTime + SleepTimer){   
      Serial.print("Going to sleep \n");
      pinMode(22, OUTPUT); // Set the CAN Rx pin as an output to override FlexCAN to allow wakeup inturrupt on CAN activity
      SPI.end();
      delay(2000);
      who = Snooze.hibernate( config_teensy40 );// return module that woke processor
  }
}
That is I see in loop where you SPI.end ... and call hibernate but how does this wake up, and who does anything at this point with SPI?
 
So this is just a simple sketch to try and drill down to what is causing the issue, which is somewhere in the SPI region.

When you power up the T4 it goes through the setup as usual, digital.pinMode(22, INPUT, RISING) sets the wake up trigger as pin 22 going high after the Teensy goes into hibernate. I just wired a push button to pin 22 and 3v3.

In the loop, after 30 seconds it goes into the shutdown process, where SPI.end is called and the hibernate command is given with the rest of the snooze configuration (digital pin 22 and USB). You'll notice the serial monitor goes offline.

Once in hibernate, pull pin 22 high (click the pushbutton), the T4 will wake up and execute the setup function, but you'll notice it doesn't print past Serial.print("digital.Write \n"). This is what leads me to believe its getting stuck at SPI.transfer

I hope the above makes sense..
I wanted to rule out that the issue was the display libraries, as they are running SPI commands after all and your suggestion to try the different SPI commands first is what lead me to this test.
 
Again never used this stuff...

But in cases like this I wonder what the differences are... SO I hacked your program and added:
Code:
  Serial.print("digital.Write \n"); Serial.flush();
  Serial.printf("%x %x %x %x %x %x %x %x %x %x\n",
                LPSPI4_CR, LPSPI4_SR,   LPSPI4_IER, LPSPI4_DER, LPSPI4_CFGR0,
                LPSPI4_CFGR1, LPSPI4_CCR, LPSPI4_FCR, LPSPI4_FSR, LPSPI4_TCR); Serial.flush();

  SPI.transfer(0x88);
Note: I add in Serial.flush() in several places to force data out, so if it dies/hangs you know that
a line actually ran or not...

So when I run your sketch, the first pass in setup, that printf shows:
Code:
1 1 0 0 0 3 3b3b76 0 0 7

Now when it stops and then wiggle pin, it resets and I see:
Code:
digital.Write 
1 0 0 0 0 3 3b3b76 0 2 1f
So CR register is the same,

But SR is different: The TDF flag is not set (so it is not wanting to send data...

The last two are different as well
FSR initial 0 after snooze 2...

So it says it has 2 entries is TX Fifo.

Transmit Command register is different as well (TCR)
7 - > 1F

So word size changed from 8 to 32 bits...
Note 32 is the default reset size...
 
Is there a legend that details what each register represents and so forth? No much info on the forum or Dr Google.

If I understand, we need to get the word size changed back to 0x0000007, clear the Tx to 0 (perhaps in the begin function, if its not set to 0), and what about SR?
I read somewhere on the NXP forum that TDF and RDF flags are read only and cannot be changed, so how does one get into a state that's the same as it is on a startup from power up? (1 1 0 0 0 3 3b3b76 0 0 7)
 
All of the information comes from the documentation on the processor. You can download the reference documents for all of the teensy processors up on the main website: https://www.pjrc.com/teensy/datasheets.html

If you look at the processor reference: The whole chapter 47 is on the LPSPI sub-system. With 47.4.1 describing all of the bits about the registers.

Source code wise, all of the registers for the Teensy 4.x processors are defined in the file ..\avr\cores\teeny4\imxrt.h

They are defined two different ways: The quick and dirty way I showed them in the sketch, like: LPSPI4_CR
We know that our main SPI object internally uses LPSPI4 hardware.
Code:
#define LPSPI4_CR			(IMXRT_LPSPI4.offset010)
Looks like we did not redefine these off of the later defined structure, like we did for some sub-systems, but this is saying it is in the field is offxet 0x10 bytes into the LPSPI4 data
Which again matches the data in the table about registers Page 2939

Or you can use the structure defined in imxrt.h (about line 6592) for the LPSPI registers.
So can address like: IMXRT_LPSPI4_S.CR

Which is more or less what SPI library does. It has a pointer to the structure, which the internal inline method port() returns a reference to.


So you will see statements like: port().CR = LPSPI_CR_RST;
Which translates to set the CR register (Control) to LSPCI_CR_RST which again all of these bits are defined in IMXRT.h with the naming convention of:
LPSPI -> LPSPI Subsystem
CR -> relates to CR register
RST-> which option/field in the register.

So in the manual page 2942 you see RST is Software Reset...
 
I have done some additional testing and things are sort of screwy... Hopefully someone who uses and knows snooze can debug more.

It is almost like some timer is not working or...
 
Thank Kurt. I’ll start reviewing the documentation over the weekend and try understand how all of this works.
Meanwhile I’ll have to let @duff take a look at this as he knows Snooze best being that he’s the author of the library.
 
Just an update to show you some of the stuff that I have been outputting and trying...

The example sketch has:
Code:
#include "SPI.h"
#include <Snooze.h>
SnoozeDigital digital;
SnoozeUSBSerial usb;
SnoozeSPI spi;
SnoozeBlock config_teensy40(usb, digital);


void setup() {
  while (!Serial && millis() < 5000);
  Serial.begin(115200);
  Serial.print("Going into Setup \n");
  delay(5);
  uint32_t n = CCM_ANALOG_PLL_USB1; // pg 759
  Serial.printf("CCM_ANALOG_PLL_USB1=%08lX\n", n);
  // CCM_ANALOG_PLL_USB1=80003040
  //    13110d0c 13110d0c

  Serial.printf("    %08lx %08lx\n", CCM_ANALOG_PFD_480_SET, CCM_ANALOG_PFD_480); 
  pinMode(10, OUTPUT);
  for(uint8_t i=0; i < 5; i++) {
    digitalWriteFast(10, HIGH);
    delay(5);
    digitalWriteFast(10, LOW);
    delay(5);
  }

  // What happens if I reset the PFDS?
  Serial.println("Before reset PFDS"); Serial.flush();
  CCM_ANALOG_PFD_528_SET = (1 << 31) | (1 << 23) | (1 << 15) | (1 << 7);
  CCM_ANALOG_PFD_528 = 0x2018101B; // PFD0:352, PFD1:594, PFD2:396, PFD3:297 MHz  
  //PLL3:
  CCM_ANALOG_PFD_480_SET = (1 << 31) | (1 << 23) | (1 << 15) | (1 << 7);  
  CCM_ANALOG_PFD_480 = 0x13110D0C; // PFD0:720, PFD1:664, PFD2:508, PFD3:454 MHz
  Serial.println("After reset PFDS"); Serial.flush();  
  SPI.begin();
  Serial.print("SPI.begin \n");
  delay(5);
  SPI.beginTransaction(SPISettings(2000000, MSBFIRST, SPI_MODE0));
  digitalWriteFast(10, LOW);
  Serial.print("SPI.beginTransaction \n");
  Serial.print("SPI.endTransaction \n"); Serial.flush();
  Serial.print("digital.Write \n"); Serial.flush();
  Serial.printf("%x %x %x %x %x %x %x %x %x %x\n",
                LPSPI4_CR, LPSPI4_SR,   LPSPI4_IER, LPSPI4_DER, LPSPI4_CFGR0,
                LPSPI4_CFGR1, LPSPI4_CCR, LPSPI4_FCR, LPSPI4_FSR, LPSPI4_TCR); Serial.flush();

  Serial.printf("%x %x %x %x %x %x %x %x %x %x\n",
                IMXRT_LPSPI4_S.CR, IMXRT_LPSPI4_S.SR,   IMXRT_LPSPI4_S.IER, IMXRT_LPSPI4_S.DER, IMXRT_LPSPI4_S.CFGR0,
                IMXRT_LPSPI4_S.CFGR1, IMXRT_LPSPI4_S.CCR, IMXRT_LPSPI4_S.FCR, IMXRT_LPSPI4_S.FSR, IMXRT_LPSPI4_S.TCR); Serial.flush();
/*
First: 1 1 0 0 0 3 3b3b76 f 0 7
Second:1 1 0 0 0 3 3b3b76 f 2 1f
beginTransaction: 3d0900=0 cbcmr=a9ae8314 clkhz=e4e1c00 _ccr=1d1d3a
SPI.begin 
beginTransaction: 1e8480=3d0900 cbcmr=a9ae8314 clkhz=e4e1c00 _ccr=3b3b76

*/
  SPI.transfer(0x88);
  Serial.print("SPI transfer 0x88 \n"); Serial.flush();
  SPI.transfer(0x03);
  Serial.print("SPI transfer 0x03 \n"); Serial.flush();
  digitalWrite(10, HIGH);
  SPI.endTransaction();

  Serial.print("digital.Write \n");
  Serial.print("SPI init \n");


  digital.pinMode(22, INPUT, RISING);//pin, mode, type
  Serial.print("Snooze digital.pinMode init \n");
}


const long SleepTimer = 30000; // Time to go to sleep
unsigned long startTime = 0;
void loop() {
  int who;
  if (millis() > startTime + SleepTimer) {
    Serial.print("Going to sleep \n");
    pinMode(22, OUTPUT); // Set the CAN Rx pin as an output to override FlexCAN to allow wakeup inturrupt on CAN activity
    SPI.end();
    delay(2000);
    who = Snooze.hibernate( config_teensy40 );// return module that woke processor
  }
}

Output: first pass:
Code:
Going into Setup 
CCM_ANALOG_PLL_USB1=80003040
    13110d0c 13110d0c
Before reset PFDS

After reset PFDS

SPI.begin port(403a0000)
SPI.begin set CR before(0) SR(1)
SPI.begin set TXWater

SPI.begin CR:0 FCR:f FSR:0 TCR:1f
beginTransaction: 3d0900=0 cbcmr=e9ae8114 Raw Clock(720000000) clkhz=240000000 _ccr=1d1d3a _tcr=7 
SPI.begin after transaction CR:1 FCR:f FSR:0 TCR:7
SPI.begin 
beginTransaction: 1e8480=3d0900 cbcmr=e9ae8114 Raw Clock(720000000) clkhz=240000000 _ccr=3b3b76 _tcr=7 
SPI.beginTransaction 
SPI.endTransaction 
digital.Write 
1 1 0 0 0 3 3b3b76 f 0 7
1 1 0 0 0 3 3b3b76 f 0 7
SPI transfer 0x88 
SPI transfer 0x03 
digital.Write 
SPI init 
Snooze digital.pinMode init 
Going to sleep
And when I touch the IO pin which causes it to wake up
Code:
Going into Setup 
CCM_ANALOG_PLL_USB1=80003040
    13110d0c 13110d0c
Before reset PFDS

After reset PFDS

SPI.begin port(403a0000)
SPI.begin set CR before(0) SR(1)
SPI.begin set TXWater

SPI.begin CR:0 FCR:f FSR:0 TCR:1f
beginTransaction: 3d0900=0 cbcmr=e9ae8114 Raw Clock(720000000) clkhz=240000000 _ccr=1d1d3a _tcr=7 
SPI.begin after transaction CR:1 FCR:f FSR:1 TCR:1f
SPI.begin 
beginTransaction: 1e8480=3d0900 cbcmr=e9ae8114 Raw Clock(720000000) clkhz=240000000 _ccr=3b3b76 _tcr=7 
SPI.beginTransaction 
SPI.endTransaction 
digital.Write 
1 1 0 0 0 3 3b3b76 f 2 1f
1 1 0 0 0 3 3b3b76 f 2 1f

Some additional changes were made to SPI library to try things, plus output debug stuff:
Like in SPI.cpp in begin:
Code:
void SPIClass::begin()
{

	// CBCMR[LPSPI_CLK_SEL] - PLL2 = 528 MHz
	// CBCMR[LPSPI_PODF] - div4 = 132 MHz


	hardware().clock_gate_register &= ~hardware().clock_gate_mask;

	CCM_CBCMR = (CCM_CBCMR & ~(CCM_CBCMR_LPSPI_PODF_MASK | CCM_CBCMR_LPSPI_CLK_SEL_MASK)) |
		CCM_CBCMR_LPSPI_PODF(2) | CCM_CBCMR_LPSPI_CLK_SEL(1); // pg 714
//		CCM_CBCMR_LPSPI_PODF(6) | CCM_CBCMR_LPSPI_CLK_SEL(2); // pg 714

	uint32_t fastio = IOMUXC_PAD_DSE(7) | IOMUXC_PAD_SPEED(2);
	//uint32_t fastio = IOMUXC_PAD_DSE(6) | IOMUXC_PAD_SPEED(1);
	//uint32_t fastio = IOMUXC_PAD_DSE(3) | IOMUXC_PAD_SPEED(3);
	//Serial.printf("SPI MISO: %d MOSI: %d, SCK: %d\n", hardware().miso_pin[miso_pin_index], hardware().mosi_pin[mosi_pin_index], hardware().sck_pin[sck_pin_index]);
	*(portControlRegister(hardware().miso_pin[miso_pin_index])) = fastio;
	*(portControlRegister(hardware().mosi_pin[mosi_pin_index])) = fastio;
	*(portControlRegister(hardware().sck_pin[sck_pin_index])) = fastio;

	//printf("CBCMR = %08lX\n", CCM_CBCMR);
	hardware().clock_gate_register |= hardware().clock_gate_mask;
	*(portConfigRegister(hardware().miso_pin[miso_pin_index])) = hardware().miso_mux[miso_pin_index];
	*(portConfigRegister(hardware().mosi_pin [mosi_pin_index])) = hardware().mosi_mux[mosi_pin_index];
	*(portConfigRegister(hardware().sck_pin [sck_pin_index])) = hardware().sck_mux[sck_pin_index];

	// Set the Mux pins 
	//Serial.println("SPI: Set Input select registers");
	hardware().sck_select_input_register = hardware().sck_select_val[sck_pin_index];
	hardware().miso_select_input_register = hardware().miso_select_val[miso_pin_index];
	hardware().mosi_select_input_register = hardware().mosi_select_val[mosi_pin_index];

	//digitalWriteFast(10, HIGH);
	//pinMode(10, OUTPUT);
	//digitalWriteFast(10, HIGH);
	Serial.printf("SPI.begin port(%x)\n", (uint32_t)&port());
	Serial.printf("SPI.begin set CR before(%x) SR(%x)\n", port().CR, port().SR);
	port().CR = LPSPI_CR_RST;
	[COLOR="#FF0000"]delay(1);
	port().CR = LPSPI_CR_RRF | LPSPI_CR_RTF;[/COLOR]
	// Lets initialize the Transmit FIFO watermark to FIFO size - 1... 
	// BUGBUG:: I assume queue of 16 for now...
	Serial.println("SPI.begin set TXWater");
	port().FCR = LPSPI_FCR_TXWATER(15);
	Serial.printf("SPI.begin CR:%x FCR:%x FSR:%x TCR:%x\n", port().CR, port().FCR,port().FSR, port().TCR);
	// We should initialize the SPI to be in a known default state.
	_clock = 0;	// make sure it sets stuff
	beginTransaction(SPISettings());
	Serial.printf("SPI.begin after transaction CR:%x FCR:%x FSR:%x TCR:%x\n", port().CR, port().FCR,port().FSR, port().TCR);
	endTransaction();
}
The serial prints are added, plus the two lines in RED, where I found that changes made to other registers while logically in reset mode (port().CR = LPSPI_CR_RST;
appear to be ignored. In particular I was seeing that with FCR setting below it...

Also in SPI.h I updated the beginTransaction code to add prints...
Code:
void beginTransaction(SPISettings settings) {
		if (interruptMasksUsed) {
			__disable_irq();
			if (interruptMasksUsed & 0x01) {
				interruptSave[0] = NVIC_ICER0 & interruptMask[0];
				NVIC_ICER0 = interruptSave[0];
			}
			if (interruptMasksUsed & 0x02) {
				interruptSave[1] = NVIC_ICER1 & interruptMask[1];
				NVIC_ICER1 = interruptSave[1];
			}
			if (interruptMasksUsed & 0x04) {
				interruptSave[2] = NVIC_ICER2 & interruptMask[2];
				NVIC_ICER2 = interruptSave[2];
			}
			if (interruptMasksUsed & 0x08) {
				interruptSave[3] = NVIC_ICER3 & interruptMask[3];
				NVIC_ICER3 = interruptSave[3];
			}
			if (interruptMasksUsed & 0x10) {
				interruptSave[4] = NVIC_ICER4 & interruptMask[4];
				NVIC_ICER4 = interruptSave[4];
			}
			__enable_irq();
		}
		#ifdef SPI_TRANSACTION_MISMATCH_LED
		if (inTransactionFlag) {
			pinMode(SPI_TRANSACTION_MISMATCH_LED, OUTPUT);
			digitalWrite(SPI_TRANSACTION_MISMATCH_LED, HIGH);
		}
		inTransactionFlag = 1;
		#endif

		//printf("trans\n");
		if (settings.clock() != _clock) {
			Serial.printf("beginTransaction: %x=%x", settings.clock(), _clock);
			static const uint32_t clk_sel[4] = {664615384,  // PLL3 PFD1
						     720000000,  // PLL3 PFD0
						     528000000,  // PLL2
						     396000000}; // PLL2 PFD2				

		    // First save away the new settings..
		    _clock = settings.clock();

			uint32_t cbcmr = CCM_CBCMR;
			uint32_t clkhz = clk_sel[(cbcmr >> 4) & 0x03] / (((cbcmr >> 26 ) & 0x07 ) + 1);  // LPSPI peripheral clock
			
			uint32_t d, div;		
			d = _clock ? clkhz/_clock : clkhz;

			if (d && clkhz/d > _clock) d++;
			if (d > 257) d= 257;  // max div
			if (d > 2) {
				div = d-2;
			} else {
				div =0;
			}
	
			_ccr = LPSPI_CCR_SCKDIV(div) | LPSPI_CCR_DBT(div/2) | LPSPI_CCR_PCSSCK(div/2);

			Serial.printf(" cbcmr=%x Raw Clock(%u) clkhz=%u _ccr=%x _tcr=%x \n", cbcmr, clk_sel[(cbcmr >> 4) & 0x03], clkhz, _ccr,
					 settings.tcr);
		} 
		//Serial.printf("SPI.beginTransaction CCR:%x TCR:%x\n", _ccr, settings.tcr);
		port().CR = 0;
		port().CFGR1 = LPSPI_CFGR1_MASTER | LPSPI_CFGR1_SAMPLE;
		port().TCR = settings.tcr;
		port().CCR = _ccr;
		port().CR = LPSPI_CR_MEN;

	}
I also played around with order of setting some of these values... But did not make difference.

Again the interesting thing is that nothing comes out of the TX FIFO after the reset including the TCR register settings, which don't logically go out to the pins but instead when they are processed, they update things like which CS pin to assert, how many bits should output....

Which gets back to the intitial stuff I noticed, when I printed out a bunch of the SPI registers.
Code:
  Serial.printf("%x %x %x %x %x %x %x %x %x %x\n",
                LPSPI4_CR, LPSPI4_SR,   LPSPI4_IER, LPSPI4_DER, LPSPI4_CFGR0,
                LPSPI4_CFGR1, LPSPI4_CCR, LPSPI4_FCR, LPSPI4_FSR, LPSPI4_TCR); Serial.flush();
Before Snooze: 1 1 0 0 0 3 3b3b76 f 0 7
After: 1 1 0 0 0 3 3b3b76 f 2 1f

Notice the last two values: FSR(Fifo Status register) the first shows TX fifo empty. The 2nd shows 2 things in it.
The last is the (Transmit Control Register) first shows 7 which is 8 bit transfers (what we pushed in the TCR)... The seond shows 0x1f=31 or 32 bit transfers, which is default power up value,
so again the TCR output was not processed.

That is all for now... Some of the other printing shows things like different settings for the underlying CCM clocks which looks correct. I also tried resetting them to see if that made difference (NO). Also checked that our setting for what the SPI speed should be is the same, it is... So again not sure what to test next...

But in case anyone else has any ideas, I thought I would do this data dump...
 
Wow, amazing work Kurt! I only just got started on reading the user guide! :D

You've clearly demonstrated that there is an issue with the SPI register upon wakeup from hibernate, Perhaps caused by the go to sleep sequence or wake up sequence of the 1062 that might have not been properly implemented in some of the sleep modes.

Is it worth taking this up on the NXP support community to further inquire?
 
Thanks, as I mentioned, this may be more something that someone like @duff who has used the hibernate and the like to look at and reminds them of some other cases where maybe there were similar issues. Like maybe some other clock module that needs to be reset or ??? I will probably try a couple more random things before I complete throw in towel...
 
Thanks a bunch for the investigation so far! I think you've covered a ton of ground and your insights are extremely valuable to duff or anyone else who can take a deeper look into this.

I did PM duff with a link to this thread, and did see the issue raised on Github (thank you again!) - I'll have to be patient and wait for him to get to it, for now I'll just power off with an unplug of the OBD connector in my car ;)
 
Status
Not open for further replies.
Back
Top