SPI delay

Technical questions regarding the XTC tools and programming with XMOS.
User avatar
RedDave
Experienced Member
Posts: 77
Joined: Fri Oct 05, 2018 4:26 pm

SPI delay

Post by RedDave »

I am using the spi_master_if to read two bytes from an ADC. The read occurs 1us after the task is notified of the reading. See below for this code.

p_dbg provides a line that I can probe with an oscilloscope to determine when and for how long the code runs. I have also been probing the SPI lines.

In production code this code will be notified every 10us. I an currently calling it in bursts of three, 100us apart, with one burst every 3ms. [In order to keep a laser eye safe].

My observation is that the first transfer starts 3.3us after the start of my function and the second 3us after that. The whole case takes 8us. This is all acceptable. Observed by measuring spi_clk relative to p_dbg line.

However, when the same code is called 100us later, the transfer does not start until after 600us! This is, obviously, not fit for my purpose. I assume that this is due to the transaction ending and then beginning again on the same device. My understanding is that this should take just 4 (SS_DASSERT_TIME) clocks extra.

How can I get repeated SPI reads to occur in good time?

Code: Select all

#define SS_DEASSERT_TIME 4
#define SPI_DEVICE_NUMBER 0
#define SPI_CLK_SPEED 48000

on tile[0] : out port p_dbg = XS1_PORT_4A;      // XOD02/03/08/09

#define ADC_DELAY   (ticks_per_us)

void intensity_task(client spi_master_if i_spi, client trigger_if i_reading)
{
    BOOL awaitingADC = FALSE;
    int adc_time;
    timer tmr;

    unsigned short value;
    unsigned char* pValue = (unsigned char*)&value;

    while(TRUE)
    {
        select
        {
            case i_reading.trigger():
                tmr :> adc_time;
                p_dbg <: 2;
                i_reading.ack();
                adc_time += ADC_DELAY;
                awaitingADC = TRUE;
                p_dbg <: 0;
                break;
            case awaitingADC => tmr when timerafter(adc_time) :> void:
                p_dbg <: 1;
                i_spi.begin_transaction(SPI_DEVICE_NUMBER, SPI_CLK_SPEED, SPI_MODE_3);
                  // Mode 3 required to correctly read in on the correct edges and correctly start on the first bit.
                pValue[1] = i_spi.transfer8(0);
                pValue[0] = i_spi.transfer8(0);
                i_spi.end_transaction(SS_DEASSERT_TIME);
                awaitingADC = FALSE;
                p_dbg <: 0;
                break;
        }
    }
}


User avatar
mon2
XCore Legend
Posts: 1913
Joined: Thu Jun 10, 2010 11:43 am
Contact:

Post by mon2 »

Hi. Can you review this thread and post your results?

https://www.xcore.com/viewtopic.php?t=4765
User avatar
mon2
XCore Legend
Posts: 1913
Joined: Thu Jun 10, 2010 11:43 am
Contact:

Post by mon2 »

According to XMOS notes, select statement cannot make use output statements. Can you test again without use of the p_dbg port?
User avatar
mon2
XCore Legend
Posts: 1913
Joined: Thu Jun 10, 2010 11:43 am
Contact:

Post by mon2 »

Mowing the lawn and day dreaming about your case...sad but true...came up with another idea:

consider to test without this SELECT statement and instead validate that your SPI widget is able to be called repeatedly in the duration you are after. That is, perform repeated calls (while(1) loop) but after 100us delay between calls and then you can continue to use your start / end markers using p_dbg port marker.

That would be a logical first step.

Perhaps your external SPI widget is unable to keep up with the frequency of your consecutive calls? Now, if you are able to perform back to back calls to your SPI device with a uniform period then move to debug the SELECT statement (ie. remove the p_dbg output use).

Really interested in the resolution for your issues.


xmos_select.png
(94.59 KiB) Not downloaded yet
xmos_select.png
(94.59 KiB) Not downloaded yet
User avatar
RedDave
Experienced Member
Posts: 77
Joined: Fri Oct 05, 2018 4:26 pm

Post by RedDave »

mon2 wrote: Sat Aug 17, 2019 1:20 pm According to XMOS notes, select statement cannot make use output statements. Can you test again without use of the p_dbg port?
Can you point me at these notes?
I have been writing to ports all over my code in case statements and not had any issue.

Without writing to the ports I will need to develop a whole new method of timing and debugging!
mon2 wrote: Sat Aug 17, 2019 4:48 pm Mowing the lawn and day dreaming about your case...sad but true...came up with another idea:

consider to test without this SELECT statement and instead validate that your SPI widget is able to be called repeatedly in the duration you are after. That is, perform repeated calls (while(1) loop) but after 100us delay between calls and then you can continue to use your start / end markers using p_dbg port marker.

That would be a logical first step.

Perhaps your external SPI widget is unable to keep up with the frequency of your consecutive calls? Now, if you are able to perform back to back calls to your SPI device with a uniform period then move to debug the SELECT statement (ie. remove the p_dbg output use).
The ADC is very much designed to transfer data at these speeds. The clock that I am observing is generated by the XMOS. Communication is simply not initiated by the XMOS until a long time after the request goes in.

I will write some standalone code on Monday to replicate and debug this issue; including not using select.
User avatar
CousinItt
Respected Member
Posts: 360
Joined: Wed May 31, 2017 6:55 pm

Post by CousinItt »

Hi RedDave,

can you check that your code isn't blocking on i_reading.ack() during the second iteration? Maybe it's the receiving task that's causing the delay. Also, shouldn't adc_time be unsigned? Not sure how much of an effect that will have practically. Lastly, 48 Mbps is MUCH faster than the fastest timings supported by the synchronous SPI master (see the lib_spi documentation). Maybe the clock division calculation gets upset and chooses something slower. If you need that speed maybe you should switch to the async SPI master.

Can you stick a logic analyser or scope on the signals to check what's going on?

@mon2, like RedDave I use output statements all the time in select statements. So do the xmos libraries. If they say we can't, I'd very much like to know where and why.
User avatar
mon2
XCore Legend
Posts: 1913
Joined: Thu Jun 10, 2010 11:43 am
Contact:

Post by mon2 »

Please see attached.

This could be my misunderstanding on the wording of the "case statement". That is, perhaps output is not permitted in the case condition check? Noting some examples in the programming guide (specifically the UART examples), will assume this is confusion on the wording. After the case condition is true, the code block for that case may be perform output.

I think CousinItt may have nailed it, the following:

Code: Select all

#define SPI_CLK_SPEED 48000
should be

Code: Select all

#define SPI_CLK_SPEED 48
for a 48khz SPI speed. The value to be passed is in khz.

Or do you really wish to drive the SPI bus clock @ 48Mhz?




xmos_case.png
(63.64 KiB) Not downloaded yet
xmos_case.png
(63.64 KiB) Not downloaded yet
lib_spi-[userguide]_3.0.2rc1.pdf
(417.56 KiB) Downloaded 177 times
lib_spi-[userguide]_3.0.2rc1.pdf
(417.56 KiB) Downloaded 177 times
Attachments
app_simple_select_ordered_example-README_1.1.1rc0.a.pdf
(204.99 KiB) Downloaded 156 times
User avatar
CousinItt
Respected Member
Posts: 360
Joined: Wed May 31, 2017 6:55 pm

Post by CousinItt »

From the XC Reference Manual. My reading is that the "case statement" in the xmos_case quote means everything from case to colon (or semicolon if a select-function is called), so outputs are forbidden there, but not in the following statements, if any.
The select statement causes control to be transferred to one of several guarded case statements. A guarded statement may consist of an optional expression followed by an input (§9.3), a slave transaction statement (§9.10) or a function call, followed by a colon and a list of zero or more statements. If the statement before the colon is a call to a transaction function (§10.1.1) then this is considered shorthand for a slave transaction statement that performs the call. The guard expression must have arithmetic type, and it must not modify a local variable, static variable or reference parameter; any functions called within the expression, recursively, must not modify a static variable, reference parameter, or perform an input or output. The modification rules that apply to the guard expression also apply to the arguments of a call to a select function; the rules also apply to an input statement that appears before the colon, except that the input lvalue is (by definition) modified.

A guarded statement may also consist of a call to a select function (see §10.1.2) followed by a semicolon. The rules that apply to the guard expression also apply to the arguments of a call to a select function. The ports, timers and channel ends named before each colon, and as arguments to a select function, must be distinct.

When the select statement is executed, each case not guarded by a guard is enabled. For each case containing a guard, the guard expression is evaluated and, if it compares unequal to 0, the case is enabled. The behaviour of a call to a select function is the same as if the cases of the select function were included inline in the select.

Following the enabling sequence, if no cases are enabled then none of the substatements of the select is executed and the select never completes (it deadlocks). Otherwise, the select waits until an input or transaction in one of the enabled cases is ready and performs the corresponding input or transaction. If more than one of these inputs or transactions is ready then the choice of which is executed is made nondeterministically.

After performing an input or transaction, the statements following the colon of the selected case are executed.

The statements after the colon in each case statement must terminate with a break or return, so that control never flows from one case statement to the next. The exception is that a case used in a switch may contain no statements at all, for grouping multiple cases with a single body.
User avatar
RedDave
Experienced Member
Posts: 77
Joined: Fri Oct 05, 2018 4:26 pm

Post by RedDave »

I'll go through the suggestions thoroughly tomorrow.

But yes, I want a 48MHz SPI clock. This is a high speed ADC device. At 48kHz it would take 0.333ms to transfer 16 bits, even without any overhead!
User avatar
RedDave
Experienced Member
Posts: 77
Joined: Fri Oct 05, 2018 4:26 pm

Post by RedDave »

New, standalone code shows exactly the same effect.
No ack(). No select. No case.

Bit 0 (BEGIN) remains high with a period of 661us.
The begin_transaction is taking 640us.
Each transfer 2.8us
And the end_transfer 400ns.

I think that this demonstrates that it is none of the issues that you have raised.

Code: Select all

#include <platform.h>
#include <spi.h>

on tile[0] : out port p_dbg = XS1_PORT_4A;      // XOD02/03/08/09
on tile[0] : out port p_dbg2 = XS1_PORT_4E;      // XOD02/03/08/09

in buffered port:32     p_spi_miso  = on tile[0]: XS1_PORT_1A;   //
out port                p_spi_ssn[1]= on tile[0]:{XS1_PORT_1L};  // Rev P04 TDC & ADC share SSN lines with SPI_CTRL
out buffered port:32    p_spi_sclk  = on tile[0]: XS1_PORT_1C;
out buffered port:32    p_spi_mosi  = on tile[0]: XS1_PORT_1D;   //
clock clk0_spi = on tile[0]: XS1_CLKBLK_1;
out port                p_spi_ctrl  = on tile[1]:XS1_PORT_4D;  //tri-colour LED - bit 0

#define SS_DEASSERT_TIME 4
#define SPI_DEVICE_NUMBER 0
#define SPI_CLK_SPEED 48000

void test_adc(client spi_master_if i_spi)
{
    unsigned short value;
    unsigned char *pValue = (unsigned char*)&value;

    while(1)
    {
        p_dbg2 <: 1;
        i_spi.begin_transaction(SPI_DEVICE_NUMBER, SPI_CLK_SPEED, SPI_MODE_3);
        p_dbg2 <: 3;
        pValue[1] = i_spi.transfer8(0);
        p_dbg2 <: 5;
        pValue[0] = i_spi.transfer8(0);
        p_dbg2 <: 9;
        i_spi.end_transaction(SS_DEASSERT_TIME);
        p_dbg2 <: 0;
    }
}

void init_tile1()
{
    p_spi_ctrl <: 0;
}

int main()
{
    spi_master_if i_spi[1];

    par
    {
        on tile[0]: spi_master(i_spi, 1,p_spi_sclk, p_spi_mosi,p_spi_miso, p_spi_ssn, 1, clk0_spi);
        on tile[0]: test_adc(i_spi[0]);
        on tile[1]: init_tile1();
    }

    return 0;
}
Image
Attachments
adc_spi_timing.png
adc_spi_timing.png (4.59 KiB) Viewed 3266 times
adc_spi_timing.png
adc_spi_timing.png (4.59 KiB) Viewed 3266 times
Post Reply