This post is older than 2 years and might not be relevant anymore
More Info: Consider searching for newer posts

SPIM3 MOSI, CLK, and D/CX not working as expected after enabling/disabling ArrayList and using PPI and Timer

Hi,

I am using the nRF52840 with no soft device, and I am experiencing an issue with SPIM3 when turning on/off the ArrayList and Shortcut (EVENTS_END and TASKS_START) where the MOSI, CLK, and D/CX lines are not toggling as expected after sending a very large amount of bytes while also using PPI and the Timer Peripherals (following this guide devzone.nordicsemi.com/.../71994 Is there something that I missing here?  Any help would be greatly appreciated!

Here is the sequence that I am issuing:

  1. Initialize SPIM3 module at 32Mbps frequency
    1. Set Config to Active High, Leading clock edge, MSB first
    2. Set Frequency to 32Mbps
    3. Initialize all SCK, MOSI, MISO, CSN, D/CX to High Drive
    4. Set SCK, MOSI, CSN, and D/CX pin to Connected
    5. Set MISO pin to Disconnected
    6. Set IFTIMING.CSNDUR = 3
    7. Set Shortcut to Disabled
    8. Set TX.D List to Disabled
  2. Send several 8-bit commands using SPIM3 to initialize external hardware (D/CX should be low for commands and high for data, automated with SPIM3 peripheral)
    1. Set TXD.PTR, TXD.MAXCNT = 1, RXD.PTR = NULL, RXD.MAXCNT = 0, DCXCNT = 1
    2. Enable the peripheral
    3. Set TASKS_START = 1
    4. Repeat (a) through (c) for several different commands
  3. Set up PPI and Timer2 so that they can be used for the long transmission, but do not enable yet
    1. Timer2
      1. Set mode to Counter
      2. Clear any existing Timer tasks
      3. Set prescaler to 0
      4. Set Bitmode to 16 bit
      5. Enable shortcut to auto-clear when the Compare0 count is reached
      6. Set CC[0] to 4
      7. Enable Compare0 Interrupt
      8. Set Timer2 Priority to 7 and enable Timer2IRQ through NVIC
    2. PPI CH 5
      1. NRF_PPI->CH[5].EEP = (uint32_t) &NRF_SPIM3->EVENTS_END;
      2. NRF_PPI->CH[5].TEP = (uint32_t) &NRF_TIMER2->TASKS_COUNT;
    3. PPI CH 6
      1. NRF_PPI->CH[6].EEP = (uint32_t) &NRF_TIMER2->EVENTS_COMPARE[0];
      2. NRF_PPI->CH[6].TEP = (uint32_t) &NRF_SPIM3->TASKS_STOP;
      3. NRF_PPI->FORK[6].TEP = (uint32_t) &NRF_TIMER2->TASKS_STOP;
  1. Prepare ArrayList prior to long transmission
    1. ArrayList has this structure:

 

#define BUFFER_SIZE     38400

#define ARRAYLIST_SIZE  5

typedef struct ArrayList

{

     uint8_t buffer[BUFFER_SIZE];

} ArrayList_type;

ArrayList_type Frame_ArrayList[ARRAYLIST_SIZE];

  • Use a For loop to fill every buffer entry with 0xFF for all ArrayList entries (total = 38400 * 5 = 192000 locations filled with 0xFF)
  1. Repeat step 2(a) through 2(c) to send one 8-bit command (D/CX should be low, automated with SPIM3 peripheral)
  2. Enable PPI and Timer2. Then send long transmission using EasyDMA ArrayList with PPI and Timer2 to control when to stop SPIM3
    1. Enable interrupt on Compare0 for Timer2
    2. Start the Timer
    3. Enable PPI for CH5 and CH6
    4. Enable SPIM3 shortcut for EVENTS_END/TASKS_START
    5. Enable TX.D List for ArrayList
    6. Set TXD.PTR to the first entry of the ArrayList[0]
    7. Set TXD.MAXCNT = BUFFERSIZE
    8. Set RXD.PTR = NULL and RXD.MAXCNT = 0
    9. Set DCXCNT = 0
    10. Enable the SPIM3 peripheral and set TASKS_START = 1
  3. The Timer is basically counting after one whole ArrayList is sent (38400 bytes transmitted). Since Timer2 CC[0] = 4, the Timer will send an interrupt after the 4th ArrayList was sent (total 38400 * 4 = 153600 bytes transmitted).
  4. When the Timer2 Interrupt is triggered:
    1. SPIM3 should be stopped and Timer2 should be stopped from the PPI
    2. Disable SPIM3
    3. Clear / disable the Timer2 Interrupt using:
      NRF_TIMER2->INTENCLR = ( TIMER_INTENCLR_COMPARE0_Clear << TIMER_INTENCLR_COMPARE0_Pos );
    4. Clear the Timer2 tasks
    5. Clear the pending IRQ in NVIC for Timer2
    6. Disable shortcut on SPIM3
    7. Disable TXD.List
    8. Disable PPI on CH5 and CH6 using CHENCLR
    9. Repeat step 2(a) through 2(c) to send one 8-bit command (D/CX should be low for the command)

 

**Everything behaves as expected until step 8(i) where I am experiencing several issues:

  • The SPIM3 module is sending out 18 clock cycles (sometimes more or less) instead of 8 even though I set TXD.MAXCNT = 1
  • The MOSI line is sending out all “0” (MOSI is set up to send 0xAA), and sometimes “0” plus garbage data, and sometimes “0” plus the actual data I intended to send but offset
  • The D/CX line is set to “1” even though I set up DCXCNT = 1

  

In the above image, this is an example of what I see for step 8(i).  10 clock cycles are sent, followed by 8 additional clocks with MOSI actually being correct but offset by 1 clock.  D/CX is not set low as I intended.

Some interesting discoveries while debugging:

  • If I use the debugger – I set a breakpoint at step 8(i)
    • All the registers for SPIM3, TIMER2, and PPI are set up properly and as expected
      • EVENTS_STOPPED, EVENTS_END, EVENTS_ENDTX, EVENTS_ENDRX, EVENTS_STARTED are all set to “1”
      • SPIM3 shortcut and interrupt were successfully disabled and SPIM3 is currently disabled
      • MAXCNT and TXD.AMOUNT = 0x9600 (as expected since we just finished sending the 38400 bytes and haven’t set up the next 8-bit command yet)
      • PPI is cleared / disabled
    • If I manually step through steps 2(a) through 2(c) using the debugger, then everything behaves properly and works as intended (D/CX is sent properly, the correct command is sent and no extra clocks are sent)
  • If I change BUFFER_SIZE to something smaller, I tried 1000 and 5000 and 10000 instead of 38400, similar behavior is observed, but sometimes the MOSI is eventually able to send what I commanded after 10 clocks of “0” (D/CX still not responding)
  • As a side question, when I set IFTIMING.CSNDUR = 3, I am expecting this to be 46.875ns, but instead I am measuring 80-90ns on the scope. I thought the calculation was 15.625ns * 3 = 46.875ns, but am I missing something?

 

Thanks for the help!

Parents
  • Not sure if I am able to follow the entire flow here, but it seems some kind of race conditions between task and events. I think I would do some modification along the lines of:

    I would use a timer compare event (e.g. CC0 interrupt) to disable the end->start shortcut after the 3rd long transfer. Then after the 4th long transfer (e.g. CC1 interrupt) you don't need any critical timing (since there is no shortcut that will start the next transfer), and you only need to update the SPIM3 peripheral to do the final 1byte transfer and issue a start task (in other words no need to issue a stop, but instead setup an spim interrupt to trigger on END interrupt). Then on spim end interrupt you can do a cleanup of timer and spim.

    Best regards,
    Kenneth

  • Hi Kenneth,

    Thanks for your recommendation. I did try something similar, but I believe there might still be a race condition. Could you please help take a look?

    This is how I have the interrupts set up:

    - For CC[0] interrupt, I am doing the following: disable SPIM3 shortcut between end->start, enable SPIM3 interrupt on EVENTS_END, start 4th long transfer

    - For SPIM3 interrupt, I am doing the following: Disable SPIM3 ArrayList and clear interrupt, clean up Timer2 and PPI, send final 8-bit command normally

    When I run the program without the debugger, the program is getting stuck because the interrupts did not trigger. I determined this when I saw the 3 long transfers appear on the scope, but I did not see the 4th long transfer or the final 8-bit command being sent.
    When I run the program with the debugger, everything works as expected - both the interrupts are triggering and all the data is being sent as expected.

    Here is the code I modified / added:

    uint8_t G_frame_done_flag = 0;
    
    void init_PPI(void)
    {
    	NRF_PPI->CH[5].EEP = (uint32_t) &NRF_SPIM3->EVENTS_END; // probably could be more specific and put EVENTS_ENDTX
    	NRF_PPI->CH[5].TEP = (uint32_t) &NRF_TIMER2->TASKS_COUNT;
    	
    	NRF_PPI->CH[6].EEP = (uint32_t) &NRF_TIMER2->EVENTS_COMPARE[0];
    	NRF_PPI->CH[6].TEP = (uint32_t) &NRF_SPIM3->TASKS_STOP;
    	NRF_PPI->FORK[6].TEP = (uint32_t) &NRF_TIMER2->TASKS_STOP;
    }
    
    void init_timer(void)
    {
    	// set up timer mode
    	NRF_TIMER2->MODE = (TIMER_MODE_MODE_Counter << TIMER_MODE_MODE_Pos); // timer mode (rather than counter mode) requires a manual start and stop of the timer
    	
    	// clear any existing timer tasks
    	NRF_TIMER2->TASKS_CLEAR = 1;
    	
    	// set timer prescaler, 16MHz / (2^prescaler)
    	NRF_TIMER2->PRESCALER = 0;
    	
    	// sets the timer's maximum value
    	NRF_TIMER2->BITMODE = ( TIMER_BITMODE_BITMODE_16Bit << TIMER_BITMODE_BITMODE_Pos );
    	
    	// Set compare values
    	NRF_TIMER2->CC[0] = 3; // this will stop SPIM and EasyDMA
    	NRF_TIMER2->INTENSET = (TIMER_INTENSET_COMPARE0_Enabled << TIMER_INTENSET_COMPARE0_Pos);
    	
    	NVIC_SetPriority(TIMER2_IRQn, 7);
    	NVIC_EnableIRQ(TIMER2_IRQn);
    }
    
    void fill_screen(unsigned int color)
    {
    	// send RAMWR command normally without EasyDMA
    	SPIM3_Command(RAMWR);
    	
    	// set up peripherals to do a long EasyDMA screen write
    	NRF_TIMER2->TASKS_START = 1;
    	NRF_PPI->CHENSET = ( (PPI_CHENSET_CH5_Enabled << PPI_CHENSET_CH5_Pos) | (PPI_CHENSET_CH6_Enabled << PPI_CHENSET_CH6_Pos) );
    	NRF_SPIM3->SHORTS = ( SPIM_SHORTS_END_START_Enabled << SPIM_SHORTS_END_START_Pos );
    	NRF_SPIM3->TXD.LIST = ( SPIM_TXD_LIST_LIST_ArrayList << SPIM_TXD_LIST_LIST_Pos );
    	
    	// send 1 frame of data using EasyDMA and the arraylist
    	NRF_SPIM3->TXD.PTR = (uint32_t)&Frame_ArrayList[0];
    	NRF_SPIM3->TXD.MAXCNT = BUFFER_SIZE;
    	NRF_SPIM3->RXD.PTR = NULL;
    	NRF_SPIM3->RXD.MAXCNT = 0;
    	NRF_SPIM3->DCXCNT = 0; // we are only sending data here
    	NRF_SPIM3->ENABLE = (SPIM_ENABLE_ENABLE_Enabled << SPIM_ENABLE_ENABLE_Pos);			//enable the peripheral	
    	NRF_SPIM3->TASKS_START = 1; // eventually the interrupt will trigger and the IRQ handler will be called
    	
    	while (G_frame_done_flag == 0)
    	{
    		// wait for flag to be set by the interrupt
    	}
    
    	return;
    } // end fill_screen
    
    void TIMER2_IRQHandler(void)
    {	
    	// SPIM3 and Timer2 already stopped by PPI
    		
    	// Disable SPIM3 shortcut
    	NRF_SPIM3->ENABLE = (SPIM_ENABLE_ENABLE_Disabled << SPIM_ENABLE_ENABLE_Pos);		//disable the peripheral for lowest power consumption when not in use
    	NRF_SPIM3->SHORTS = ( SPIM_SHORTS_END_START_Disabled << SPIM_SHORTS_END_START_Pos );
    		
    	// Set up SPIM3 interrupt on EVENTS_END
    	NRF_SPIM3->INTENSET = (SPIM_INTENSET_END_Enabled << SPIM_INTENSET_END_Pos);
    	NVIC_SetPriority(SPIM3_IRQn, 7);
    	NVIC_EnableIRQ(SPIM3_IRQn);
    	
    	// Clear interrupt
    	NVIC_ClearPendingIRQ(TIMER2_IRQn);
    	NRF_TIMER2->INTENCLR = 	(TIMER_INTENCLR_COMPARE0_Clear << TIMER_INTENCLR_COMPARE0_Pos);	
    	
    	// Set up last chunk of frame transfer
    	NRF_SPIM3->EVENTS_END = 0;
    	NRF_SPIM3->EVENTS_STOPPED = 0;
    	NRF_SPIM3->TXD.PTR = (uint32_t)&Frame_ArrayList[3];
    		
    	// Enable SPIM3 to transfer last data chunk
    	NRF_SPIM3->ENABLE = (SPIM_ENABLE_ENABLE_Enabled << SPIM_ENABLE_ENABLE_Pos);			//enable the peripheral	
    	NRF_SPIM3->TASKS_START = 1; // eventually the interrupt will trigger and the IRQ handler will be called
    		
    	return;
    } // end Timer2_IRQHandler
    
    void SPIM3_IRQHandler(void)
    {	
    	// Disable EasyDMA ArrayList
    	NRF_SPIM3->TXD.LIST = (SPIM_TXD_LIST_LIST_Disabled << SPIM_TXD_LIST_LIST_Pos);
    	
    	// Disable interrupt
    	NRF_SPIM3->INTENCLR = (SPIM_INTENCLR_END_Clear << SPIM_INTENCLR_END_Pos);
    	
    	// Disable PPI and Timer
    	NRF_TIMER2->TASKS_STOP = 1;
    	NRF_TIMER2->TASKS_CLEAR = 1;
    	NRF_TIMER2->EVENTS_COMPARE[0] = 0;
    	
    	NRF_PPI->CHENCLR = ( (PPI_CHENCLR_CH5_Clear << PPI_CHENCLR_CH5_Pos) | (PPI_CHENCLR_CH6_Clear << PPI_CHENCLR_CH6_Pos) );
    	
    	// Send no-op
    	NRF_SPIM3->EVENTS_END = 0;
    	NRF_SPIM3->EVENTS_STOPPED = 0;
    	SPIM3_Command(NOP_CMD);
    	
    	G_frame_done_flag = 1;
    	
    	return;
    } // end SPIM3_IRQHandler

    Thanks!

  • As I am also using a mix of short and long burst (but on nrf52832) your post was very helpful. I am using SDK 15.3 and ran into a similar problem with unexpected clock cycles after the data transfer. Will try to leverage your insights and really appreciate the details and clarity of your questions and post. If I have any worth while feedback I will make sure to provide an update. 

  • Presuming you are doing the suggestion I made.

    In that case on CC0 (after third transfer), the 4th transfer has already started due to the end->start shortcut, so I don't see any reason why you need to start the 4th transfer here. You should only need to disable the end->start shortcut on CC0, such that on CC1 (after fourth transfer), there is no automatic end->start shortcut operation in place (note: be careful using breakpoints here for debugging, since that may prevent the code to disable the end->start shortcut, thereby the hardware can be triggering several start tasks in a row while the breakpoint is set). On CC1 you will need to "clean up" the SPIM3 peripheral (e.g. disable list) to prepare for 1byte transfer, and then execute start task manually in code. It may now be an idea to setup a CC2 (triggered after fifth transfer), then all SPI transfers should be complete, so you may clean up SPIM3/timer/ppi such that it's ready for next full cycle transfers.

    Alternatively you may just send several SPI transfers without list if that works for you.

  • Hi Kenneth,

    I was calling TASKS_START for the 4th transfer because I stopped SPIM3 using PPI CH[6] when CC[0] occurred, but I understand what you are describing now and updated my code to do the following:

    On CC[0], the Timer2 interrupt function is called. I am disabling the SPIM3 end->start shortcut and enabling the SPIM3 interrupt on EVENTS_END (which I expect to be called after the 4th long transfer completes).

    Once SPIM3 completes the 4th long transfer, I expect my code to increase the Timer2 count so that CC[1] is triggered (counting via PPI) and I expect the PPI to issue TASKS_STOP to SPIM3. I also expect the SPIM3 interrupt function to be called since the event has ended. In the SPIM3 interrupt function, I am cleaning up the Timer2 and PPI so that I can send the last 8 bits manually.

    In order to not set up the debugger, I added in some GPIO toggling for troubleshooting as well.

    When I run my code with the above changes, I see that the 3 long transfers are completed, but somehow CC[1] is triggered too early and the 4th long transfer is not completed. The last 8-bits are also not completed properly (blue LED never turns on).

    Here are the parts I changed:

    void init_PPI(void)
    {
    	NRF_PPI->CH[5].EEP = (uint32_t) &NRF_SPIM3->EVENTS_END;
    	NRF_PPI->CH[5].TEP = (uint32_t) &NRF_TIMER2->TASKS_COUNT;
    	
    	NRF_PPI->CH[6].EEP = (uint32_t) &NRF_TIMER2->EVENTS_COMPARE[1];
    	NRF_PPI->CH[6].TEP = (uint32_t) &NRF_SPIM3->TASKS_STOP;
    	NRF_PPI->FORK[6].TEP = (uint32_t) &NRF_TIMER2->TASKS_STOP;
    }
    
    
    void fill_screen(unsigned int color)
    {
    	// send RAMWR command normally without EasyDMA
    	SPIM3_Command(RAMWR);
    	
    	// set up peripherals to do a long EasyDMA screen write
    	NRF_TIMER2->TASKS_START = 1;
    	NRF_PPI->CHENSET = ( (PPI_CHENSET_CH5_Enabled << PPI_CHENSET_CH5_Pos) | (PPI_CHENSET_CH6_Enabled << PPI_CHENSET_CH6_Pos) );
    	NRF_SPIM3->SHORTS = ( SPIM_SHORTS_END_START_Enabled << SPIM_SHORTS_END_START_Pos );
    	NRF_SPIM3->TXD.LIST = ( SPIM_TXD_LIST_LIST_ArrayList << SPIM_TXD_LIST_LIST_Pos );
    	
    	// send 1 frame of data using EasyDMA and the arraylist
    	NRF_SPIM3->TXD.PTR = (uint32_t)&Frame_ArrayList[0];
    	NRF_SPIM3->TXD.MAXCNT = BUFFER_SIZE;
    	NRF_SPIM3->RXD.PTR = NULL;
    	NRF_SPIM3->RXD.MAXCNT = 0;
    	NRF_SPIM3->DCXCNT = 0; // we are only sending data here
    	NRF_SPIM3->ENABLE = (SPIM_ENABLE_ENABLE_Enabled << SPIM_ENABLE_ENABLE_Pos);			//enable the peripheral	
    	NRF_SPIM3->TASKS_START = 1; // eventually the interrupt will trigger and the IRQ handler will be called
    	
    	while (G_frame_done_flag == 0)
    	{
    		// wait for flag to be set by the interrupt
    	}
    
    	// Send no-op
    	gpio_pin_set(PIN_GREEN_CONTROL);
    	NRF_SPIM3->EVENTS_END = 0;
    	NRF_SPIM3->EVENTS_STOPPED = 0;
    	SPIM3_Command(NOP_CMD);	
    	gpio_pin_set(PIN_BLUE_CONTROL);
    	return;
    } // end fill_screen
    
    
    void TIMER2_IRQHandler(void)
    {		
    	// Disable SPIM3 shortcut
    	NRF_SPIM3->SHORTS = ( SPIM_SHORTS_END_START_Disabled << SPIM_SHORTS_END_START_Pos );
    	
    	// Set up SPIM3 interrupt on EVENTS_END
    	NRF_SPIM3->INTENSET = (SPIM_INTENSET_END_Enabled << SPIM_INTENSET_END_Pos);
    	NVIC_SetPriority(SPIM3_IRQn, 7);
    	NVIC_EnableIRQ(SPIM3_IRQn);
    	
    	// Clear interrupt
    	NVIC_ClearPendingIRQ(TIMER2_IRQn);
    	NRF_TIMER2->INTENCLR = 	(TIMER_INTENCLR_COMPARE0_Clear << TIMER_INTENCLR_COMPARE0_Pos);	
    		
    	return;
    } // end Timer2_IRQHandler
    
    
    
    void SPIM3_IRQHandler(void)
    {	
    	// Timer2 and SPIM3 should have already been stopped via PPI by now
    	gpio_pin_set(PIN_RED_CONTROL);
    	
    	// Disable EasyDMA ArrayList
    	NRF_SPIM3->ENABLE = (SPIM_ENABLE_ENABLE_Disabled << SPIM_ENABLE_ENABLE_Pos);		//disable the peripheral for lowest power consumption when not in use
    	NRF_SPIM3->TXD.LIST = (SPIM_TXD_LIST_LIST_Disabled << SPIM_TXD_LIST_LIST_Pos);
    	
    	// Disable interrupt
    	NRF_SPIM3->INTENCLR = (SPIM_INTENCLR_END_Clear << SPIM_INTENCLR_END_Pos);
    	
    	// Disable PPI and Timer
    	NRF_TIMER2->TASKS_STOP = 1;
    	NRF_TIMER2->TASKS_CLEAR = 1;
    	NRF_TIMER2->EVENTS_COMPARE[0] = 0;
    	NRF_TIMER2->EVENTS_COMPARE[1] = 0;
    	
    	NRF_PPI->CHENCLR = ( (PPI_CHENCLR_CH5_Clear << PPI_CHENCLR_CH5_Pos) | (PPI_CHENCLR_CH6_Clear << PPI_CHENCLR_CH6_Pos) );
    	
    	G_frame_done_flag = 1;
    	
    	return;
    } // end SPIM3_IRQHandler

    Here is what I see on the scope:

    3 Long Transfers completed shown below

    Zoomed in screenshot of the end of the scope capture where I am not sure why the 4th long transfer is stopped early and then something else is transmitted at the end

    Any ideas for why the 4th long transfer gets stopped early?  Also for why the last 8 bits never complete properly (I am trying to send NOP_CMD = 0x00 with D/CX = "0")?  Thanks!

Reply
  • Hi Kenneth,

    I was calling TASKS_START for the 4th transfer because I stopped SPIM3 using PPI CH[6] when CC[0] occurred, but I understand what you are describing now and updated my code to do the following:

    On CC[0], the Timer2 interrupt function is called. I am disabling the SPIM3 end->start shortcut and enabling the SPIM3 interrupt on EVENTS_END (which I expect to be called after the 4th long transfer completes).

    Once SPIM3 completes the 4th long transfer, I expect my code to increase the Timer2 count so that CC[1] is triggered (counting via PPI) and I expect the PPI to issue TASKS_STOP to SPIM3. I also expect the SPIM3 interrupt function to be called since the event has ended. In the SPIM3 interrupt function, I am cleaning up the Timer2 and PPI so that I can send the last 8 bits manually.

    In order to not set up the debugger, I added in some GPIO toggling for troubleshooting as well.

    When I run my code with the above changes, I see that the 3 long transfers are completed, but somehow CC[1] is triggered too early and the 4th long transfer is not completed. The last 8-bits are also not completed properly (blue LED never turns on).

    Here are the parts I changed:

    void init_PPI(void)
    {
    	NRF_PPI->CH[5].EEP = (uint32_t) &NRF_SPIM3->EVENTS_END;
    	NRF_PPI->CH[5].TEP = (uint32_t) &NRF_TIMER2->TASKS_COUNT;
    	
    	NRF_PPI->CH[6].EEP = (uint32_t) &NRF_TIMER2->EVENTS_COMPARE[1];
    	NRF_PPI->CH[6].TEP = (uint32_t) &NRF_SPIM3->TASKS_STOP;
    	NRF_PPI->FORK[6].TEP = (uint32_t) &NRF_TIMER2->TASKS_STOP;
    }
    
    
    void fill_screen(unsigned int color)
    {
    	// send RAMWR command normally without EasyDMA
    	SPIM3_Command(RAMWR);
    	
    	// set up peripherals to do a long EasyDMA screen write
    	NRF_TIMER2->TASKS_START = 1;
    	NRF_PPI->CHENSET = ( (PPI_CHENSET_CH5_Enabled << PPI_CHENSET_CH5_Pos) | (PPI_CHENSET_CH6_Enabled << PPI_CHENSET_CH6_Pos) );
    	NRF_SPIM3->SHORTS = ( SPIM_SHORTS_END_START_Enabled << SPIM_SHORTS_END_START_Pos );
    	NRF_SPIM3->TXD.LIST = ( SPIM_TXD_LIST_LIST_ArrayList << SPIM_TXD_LIST_LIST_Pos );
    	
    	// send 1 frame of data using EasyDMA and the arraylist
    	NRF_SPIM3->TXD.PTR = (uint32_t)&Frame_ArrayList[0];
    	NRF_SPIM3->TXD.MAXCNT = BUFFER_SIZE;
    	NRF_SPIM3->RXD.PTR = NULL;
    	NRF_SPIM3->RXD.MAXCNT = 0;
    	NRF_SPIM3->DCXCNT = 0; // we are only sending data here
    	NRF_SPIM3->ENABLE = (SPIM_ENABLE_ENABLE_Enabled << SPIM_ENABLE_ENABLE_Pos);			//enable the peripheral	
    	NRF_SPIM3->TASKS_START = 1; // eventually the interrupt will trigger and the IRQ handler will be called
    	
    	while (G_frame_done_flag == 0)
    	{
    		// wait for flag to be set by the interrupt
    	}
    
    	// Send no-op
    	gpio_pin_set(PIN_GREEN_CONTROL);
    	NRF_SPIM3->EVENTS_END = 0;
    	NRF_SPIM3->EVENTS_STOPPED = 0;
    	SPIM3_Command(NOP_CMD);	
    	gpio_pin_set(PIN_BLUE_CONTROL);
    	return;
    } // end fill_screen
    
    
    void TIMER2_IRQHandler(void)
    {		
    	// Disable SPIM3 shortcut
    	NRF_SPIM3->SHORTS = ( SPIM_SHORTS_END_START_Disabled << SPIM_SHORTS_END_START_Pos );
    	
    	// Set up SPIM3 interrupt on EVENTS_END
    	NRF_SPIM3->INTENSET = (SPIM_INTENSET_END_Enabled << SPIM_INTENSET_END_Pos);
    	NVIC_SetPriority(SPIM3_IRQn, 7);
    	NVIC_EnableIRQ(SPIM3_IRQn);
    	
    	// Clear interrupt
    	NVIC_ClearPendingIRQ(TIMER2_IRQn);
    	NRF_TIMER2->INTENCLR = 	(TIMER_INTENCLR_COMPARE0_Clear << TIMER_INTENCLR_COMPARE0_Pos);	
    		
    	return;
    } // end Timer2_IRQHandler
    
    
    
    void SPIM3_IRQHandler(void)
    {	
    	// Timer2 and SPIM3 should have already been stopped via PPI by now
    	gpio_pin_set(PIN_RED_CONTROL);
    	
    	// Disable EasyDMA ArrayList
    	NRF_SPIM3->ENABLE = (SPIM_ENABLE_ENABLE_Disabled << SPIM_ENABLE_ENABLE_Pos);		//disable the peripheral for lowest power consumption when not in use
    	NRF_SPIM3->TXD.LIST = (SPIM_TXD_LIST_LIST_Disabled << SPIM_TXD_LIST_LIST_Pos);
    	
    	// Disable interrupt
    	NRF_SPIM3->INTENCLR = (SPIM_INTENCLR_END_Clear << SPIM_INTENCLR_END_Pos);
    	
    	// Disable PPI and Timer
    	NRF_TIMER2->TASKS_STOP = 1;
    	NRF_TIMER2->TASKS_CLEAR = 1;
    	NRF_TIMER2->EVENTS_COMPARE[0] = 0;
    	NRF_TIMER2->EVENTS_COMPARE[1] = 0;
    	
    	NRF_PPI->CHENCLR = ( (PPI_CHENCLR_CH5_Clear << PPI_CHENCLR_CH5_Pos) | (PPI_CHENCLR_CH6_Clear << PPI_CHENCLR_CH6_Pos) );
    	
    	G_frame_done_flag = 1;
    	
    	return;
    } // end SPIM3_IRQHandler

    Here is what I see on the scope:

    3 Long Transfers completed shown below

    Zoomed in screenshot of the end of the scope capture where I am not sure why the 4th long transfer is stopped early and then something else is transmitted at the end

    Any ideas for why the 4th long transfer gets stopped early?  Also for why the last 8 bits never complete properly (I am trying to send NOP_CMD = 0x00 with D/CX = "0")?  Thanks!

Children
  • I think I would not enable the "SPIM3 interrupt on EVENTS_END" on CC0, because it will make the code more prune to race conditions (e.g. you will then have a SPIM3 and CC1 interrupt occur at the same time, and would need to consider interrupt priorities on which to handle first, you would also need to consider when to clear the EVENTS_END event considering a SPI transaction is in progress before enable the EVENTS_END interrupt). So instead just use TIMER interrupts, e.g. something like this:

    timer_irq_handler()
    {
    if(EVENTS_COMPARE[0] == 1) // 3 transfers
    {
    EVENTS_COMPARE[0] = 0;
    // disable END-START shortcut
    ..
    }
    if(EVENTS_COMPARE[1] ==1) // 4 transfers
    {
    EVENTS_COMPARE[1] = 0;
    // clean-up SPIM3 to prepare 1 byte transfer (disable array list)
    // trigger START of SPIM3
    ..
    }
    if(EVENTS_COMPARE[2] ==1) // 5 transfers
    {
    EVENTS_COMPARE[2] = 0;
    // all done, clean-up SPIM3, PPI and TIMER
    ..
    }
    }

    I not not understand why you want to trigger or have a TASKS_STOP here.

  • Hi Kenneth,

    That makes sense.  I actually didn't realize I could use "if(EVENTS_COMPARE[0] == 1)", so I implemented your suggestion and now everything works!!  Thanks so much for the help! 

    For others following this thread, here are the final code changes I made which ensured no extra clocks were sent after the EasyDMA transfer and ensured that D/CX was toggling properly for the last manual 8-bit transfer. 

    void init_PPI(void)
    {
    	NRF_PPI->CH[5].EEP = (uint32_t) &NRF_SPIM3->EVENTS_END;
    	NRF_PPI->CH[5].TEP = (uint32_t) &NRF_TIMER2->TASKS_COUNT;
    } // end init_PPI()
    
    void init_timer(void)
    {
    	// set up timer mode
    	NRF_TIMER2->MODE = (TIMER_MODE_MODE_Counter << TIMER_MODE_MODE_Pos); // timer mode (rather than counter mode) requires a manual start and stop of the timer
    	
    	// clear any existing timer tasks
    	NRF_TIMER2->TASKS_CLEAR = 1;
    	
    	// set timer prescaler, 16MHz / (2^prescaler)
    	NRF_TIMER2->PRESCALER = 0;
    	
    	// sets the timer's maximum value
    	NRF_TIMER2->BITMODE = ( TIMER_BITMODE_BITMODE_16Bit << TIMER_BITMODE_BITMODE_Pos );
    	
    	// Set compare values with Interrupt
    	NRF_TIMER2->CC[0] = 3; // used to turn off SPIM shortcut to prevent pointer overflows / pointer going to unknown address which sends garbage data
    	NRF_TIMER2->CC[1] = 4; // used to send last 8-bit SPIM command
    	NRF_TIMER2->CC[2] = 5; // used to clean up PPI and Timer2
    	NRF_TIMER2->INTENSET = ( (TIMER_INTENSET_COMPARE0_Enabled << TIMER_INTENSET_COMPARE0_Pos) |
    	                         (TIMER_INTENSET_COMPARE1_Enabled << TIMER_INTENSET_COMPARE1_Pos) | 
    	                         (TIMER_INTENSET_COMPARE2_Enabled << TIMER_INTENSET_COMPARE2_Pos) 
    	                       );
    	
    	NVIC_SetPriority(TIMER2_IRQn, 7);
    	NVIC_EnableIRQ(TIMER2_IRQn);
    } // end init_timer()
    
    void fill_screen(unsigned int color)
    {
    	// Clear Flag
    	G_frame_done_flag = 0;
    	
    	// send RAMWR command normally without EasyDMA
    	SPIM3_Command(RAMWR);
    	
    	// set up peripherals to do a long EasyDMA screen write
    	NRF_TIMER2->TASKS_START = 1;
    	NRF_PPI->CHENSET = ( PPI_CHENSET_CH5_Enabled << PPI_CHENSET_CH5_Pos );
    	NRF_SPIM3->SHORTS = ( SPIM_SHORTS_END_START_Enabled << SPIM_SHORTS_END_START_Pos );
    	NRF_SPIM3->TXD.LIST = ( SPIM_TXD_LIST_LIST_ArrayList << SPIM_TXD_LIST_LIST_Pos );
    	
    	// send 1 frame of data using EasyDMA and the arraylist
    	NRF_SPIM3->TXD.PTR = (uint32_t)&Frame_ArrayList[0];
    	NRF_SPIM3->TXD.MAXCNT = BUFFER_SIZE;
    	NRF_SPIM3->RXD.PTR = NULL;
    	NRF_SPIM3->RXD.MAXCNT = 0;
    	NRF_SPIM3->DCXCNT = 0; // we are only sending data here
    	NRF_SPIM3->ENABLE = (SPIM_ENABLE_ENABLE_Enabled << SPIM_ENABLE_ENABLE_Pos);			//enable the peripheral	
    	NRF_SPIM3->TASKS_START = 1; // eventually the interrupt will trigger and the IRQ handler will be called
    	
    	while (G_frame_done_flag == 0)
    	{
    		// Wait for flag to be set by the interrupt
    		// All commands will be sent using the Timer2 IRQ handler
    	}
    	
    	return;
    } // end fill_screen
    
    void TIMER2_IRQHandler(void)
    {		
    	// CC[0] - triggered after 3 long transfers
    	if (NRF_TIMER2->EVENTS_COMPARE[0] == 1)
    	{
    		// Clear event
    		NRF_TIMER2->EVENTS_COMPARE[0] = 0;
    		
    		// Disable SPIM3 shortcut
    		NRF_SPIM3->SHORTS = ( SPIM_SHORTS_END_START_Disabled << SPIM_SHORTS_END_START_Pos );
    		
    		// Clear interrupt
    		NVIC_ClearPendingIRQ(TIMER2_IRQn);
    	} // end if
    	
    	// CC[1] - triggered after the 4th long transfer
    	if (NRF_TIMER2->EVENTS_COMPARE[1] == 1)
    	{
    		// Clear event
    		NRF_TIMER2->EVENTS_COMPARE[1] = 0;
    	
    		// Disable EasyDMA ArrayList
    		NRF_SPIM3->TXD.LIST = (SPIM_TXD_LIST_LIST_Disabled << SPIM_TXD_LIST_LIST_Pos);
    		
    		// Send no-op
    		NRF_SPIM3->EVENTS_END = 0;
    		NRF_SPIM3->EVENTS_STOPPED = 0;
    		SPIM3_Command(NOP_CMD);	
    		
    		// Clear interrupt
    		NVIC_ClearPendingIRQ(TIMER2_IRQn);
    	} // end if
    	
    	// CC[2] - triggered after the 5th transfer (manual 8-bit transfer)
    	if (NRF_TIMER2->EVENTS_COMPARE[2] == 1)
    	{
    		// Clear event
    		NRF_TIMER2->EVENTS_COMPARE[2] = 0;
    		
    		// Disable PPI and Timer
    		NRF_TIMER2->TASKS_STOP = 1;
    		NRF_TIMER2->TASKS_CLEAR = 1;		
    		NRF_PPI->CHENCLR = ( PPI_CHENCLR_CH5_Clear << PPI_CHENCLR_CH5_Pos );
    		
    		// Clear interrupt
    		NVIC_ClearPendingIRQ(TIMER2_IRQn);
    		
    		// Set flag
    		G_frame_done_flag = 1;
    	} // end if
    	
    	return;
    } // end Timer2_IRQHandler

    Scope shots:

Related