SPI library as slave (AN00161)

Technical questions regarding the XTC tools and programming with XMOS.
Post Reply
mx12
Member++
Posts: 20
Joined: Thu Aug 18, 2016 3:22 pm

SPI library as slave (AN00161)

Post by mx12 »

Hi,
I try to interface my XMOS chip with a Blackfin DSP, using SPI, Blackfin as master, XMOS as slave. I followed the AN00161, "How to use the SPI library as SPI slave", so that I can try to read SPI registers from Blackfin. The registers are initialized to a dedicated value in the XMOS app init:

Code: Select all

  spi_reg.set_reg(0, 0xA0);
  spi_reg.set_reg(1, 0xA1);
  spi_reg.set_reg(2, 0xA2);
  spi_reg.set_reg(3, 0xA3);
  spi_reg.set_reg(4, 0xA4);
On the Blackfin DSP side, I read those registers using the following code:

Code: Select all

UN8 xmos_reg[5];

UN8 xmos_read_reg(UN8 reg)
{
	UN8 b;
	cpu_gpio_clear(spi_cs);
	cpu_spi_write(spi, READ_REG);
	cpu_spi_write(spi, reg);
	b = cpu_spi_write(spi, 0);
	cpu_gpio_set(spi_cs);
	return b;
}

xmos_reg[0] = xmos_read_reg(0);
xmos_reg[1] = xmos_read_reg(1);
xmos_reg[2] = xmos_read_reg(2);	
xmos_reg[3] = xmos_read_reg(3);	
xmos_reg[4] = xmos_read_reg(4);
My Blackfin SPI code is tested, I use it for years.

What I expected:

Code: Select all

xmos_reg[0]: 0xA0;
xmos_reg[1]: 0xA1;
xmos_reg[2]: 0xA2;	
xmos_reg[3]: 0xA3;	
xmos_reg[4]: 0xA4;
What I get:

Code: Select all

xmos_reg[0]: 0xA0;
xmos_reg[1]: 0xA0;
xmos_reg[2]: 0xA1;	
xmos_reg[3]: 0xA2;	
xmos_reg[4]: 0xA3;
Repeating the read process gives always the following:

Code: Select all

xmos_reg[0]: 0xA4;
xmos_reg[1]: 0xA0;
xmos_reg[2]: 0xA1;	
xmos_reg[3]: 0xA2;	
xmos_reg[4]: 0xA3;
I tried all the SPI modes, with no success.


There is also another strange behavior: the MISO signal seems to be driven by the XMOS even after the SS becomes high. Is this a known bug?

Any help warmly appreciated. Thanks for your support,
Vincent


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

Post by mon2 »

Hi Vincent. Have not used the SPI library but a few questions and comments:

1) what is the value of SCLK generated by your Blackfin ? Can you test with a slower clock ? Are the results the same or different with a slower SCLK ?

2) is the XMOS CPU and your Blackfin on the same PCB ? If not, what are the lengths of your wiring ? Consider to test with the smallest wire length that is practical.

3) consider to test the AN00161 as posted to confirm the demo operates correctly. Does that work for your testing ?

4) if possible, post your XMOS source code for a review.

Are you using this source ?

https://github.com/xmos/lib_spi/blob/ma ... rc/main.xc

Also, have a study of the following SPI slave example:

http://www.xcore.com/viewtopic.php?t=5404
mx12
Member++
Posts: 20
Joined: Thu Aug 18, 2016 3:22 pm

Post by mx12 »

Hi mon2,

Thanks for your answer. Yes, I used the AN00161 as reference code, and copied the reg_file task for my purposes.

1) SCLK frequency: I use 125kHz, but tried 500k, 1M, 2M, 4M and 8MHz. It gives similar results from 125k to 2M. 4 & 8MHz gives unpredictable results.
2) No, the XMOS is on a separate board with a 1.5cm ZIF cable interconnect. Wires length are < 10cm from Blackfin to XMOS, signals timing & edges are OK on scope. Due to the fact I cannot boot the XMOS using SPI slave (see my post here), I added a SPI flash close to the XMOS. The Blackfin is able to write to this memory device @8MHz.
3) Well, I just wrote my own SPI code. No time for testing the AN00161, sorry, but as far I can see, this app note will not demonstrate my problem (only one register is written). Time is very problematic for this project as I lost a lot of time with XMOS booting & interfacing with Blackfin.
4) see 3)


The next thing I need to fix is that MISO line, which remains driven even when SS is high. In my understanding, as soon as the XMOS is booted, the MISO line is driven (as configured as output). Am I right? If yes, this is really unacceptable, and do not follow SPI interface (which allow to connect multiple slaves to one master). What would you recommend to fix that:
  • shall I reconfigure port each time SS line change (software fix)? How?
  • shall I add a hardware buffer with SS as output enable (hardware fix)?
  • other suggestion(?)
Thanks,
Vincent

P.S.
There are some inconsistencies in the documentation:
  • If MISO line is driven even when the SS is not active, THIS SHOULD BE CLEARLY VISIBLE in the documentation
  • SPI Library (3.0.2) PDF documentation, page 6:
    The slave will only send and receive data when the slave select is driven high.
    ..should be "when the slave select is driven low" or "when the slave select is active"
  • Timings are very critical in SPI slave operation. I had to add a lot of active wait during the SPI transfer to make it working. No timing is given in the documentation, no max SCLK frequency, nothing.
  • AN00161PDF, page 8, chapter 2.5,

    Code: Select all

    reg.set_reg(0, 0xfe);
    debug_printf("APP: Set register 1 to 0xFE\n");
    
    should be "debug_printf("APP: Set register 0 to 0xFE\n");"
User avatar
mon2
XCore Legend
Posts: 1913
Joined: Thu Jun 10, 2010 11:43 am
Contact:

Post by mon2 »

Hi. I think you have a very strong handle on this and can only offer a listening ear as we have not yet worked with the same SPI code..

1) for now, do not be concerned about multiple SPI slave support and work only point to point between your SPI master and this XMOS SPI slave code till you have the XMOS code fully debugged

2) agree that if SS on the SPI slave is HIGH then the MISO line should tri-state. You should be able to turn around the respective port pin in the XMOS code but if you can support the idea, apply a single SOT-23 buffer like 74AHC1G125:

https://www.diodes.com/products/logic/s ... 74AHC1G125

We are finding that Diodes Inc. is setting the bar for aggressive pricing on such tiny logic devices.

Then when SS is high (tied to the 125's gate enable), this gate will tri-state the XMOS port pin to the outside world. This way, you do not have to mess with the turn around issue of the XMOS port pin for MISO.

No promises but will try to review this code between now and Monday - have some mini fires to put out and waiting on a delivery from Digikey. SPI slave looks like a nice idea we can also use in the future.

BTW - have you placed the XMOS SPI Slave onto a logic analyzer ? Saleae or similar ?
mx12
Member++
Posts: 20
Joined: Thu Aug 18, 2016 3:22 pm

Post by mx12 »

Right, I'll add the buffer for MISO.

I copy here my SPI slave code, if you have time to review it or for anyone that is interested:

spi_slave.h:

Code: Select all

/**
 * A register based SPI slave implementation
 * SPI is limited to 8-bit mode 0
 */

typedef interface spi_reg_if {
    uint8_t get_reg(uint8_t regnum);
    void set_reg(uint8_t regnum, uint8_t value);
} spi_reg_if;


[[combinable]]
 void spi_slave(server spi_reg_if spi_i,
                in port p_sclk,
                in buffered port:8 p_mosi,
                out buffered port:8 p_miso,
                in port p_ss,
                clock clk);
spi_slave.c:

Code: Select all

#include <xs1.h>
#include <xclib.h>
#include <stdio.h>
#include <stdlib.h>

#include <platform.h>
#include <print.h>
#include <string.h>
#include <xscope.h>
#include "gpio.h"
#include "xassert.h"
#include "debug_print.h"
#include "spi_slave.h"

#define SPI_REG_COUNT           3


#define SPI_STATE_IDLE          0
#define SPI_STATE_WRITE_REG     1
#define SPI_STATE_READ_REG      2

#define ASSERTED                1



[[combinable]]
void spi_slave(server spi_reg_if spi_i,
               in port sclk,
               in buffered port:8 mosi,
               out buffered port:8 miso,
               in port ss,
               clock clk){

     // SPI registers, map address & state
    uint8_t reg_data[SPI_REG_COUNT] = {0};
    uint8_t addr = 0;
    uint8_t state = SPI_STATE_IDLE;

     // ports setup
     set_port_inv(ss);
     stop_clock(clk);
     set_clock_src(clk, sclk);
     configure_out_port_strobed_slave(miso, ss, clk, 0);
     configure_in_port_strobed_slave(mosi, ss, clk);
     start_clock(clk);
     set_port_inv(sclk);    // for mode 0
     sync(sclk);

     int ss_val;
     uint32_t buffer;

     ss when pinseq(!ASSERTED) :> ss_val;
     while(1){
         select {
             //Wait for select line to change
             case ss when pinsneq(ss_val) :> ss_val:{
                 // SS rising edge => end of transfer
                 clearbuf(miso);
                 clearbuf(mosi);
                 state = SPI_STATE_IDLE;
                 break;
             }

             case mosi :> int i:{

                 // MOSI
                 buffer = bitrev(i) >> 24;
                 switch (state) {
                     case SPI_STATE_IDLE:
                     addr = buffer & 0x7F;
                     if(buffer & 0x80)
                         state = SPI_STATE_READ_REG;
                     else
                         state = SPI_STATE_WRITE_REG;
                     break;

                     case SPI_STATE_READ_REG:
                     break;

                     case SPI_STATE_WRITE_REG:
                     reg_data[addr] = buffer;
                     break;
                 }

                 // MISO
                 buffer = reg_data[addr];
                 buffer = (bitrev(buffer)>>24);
                 miso <: buffer;
                 break;
             }

             case spi_i.get_reg(uint8_t regnum) -> uint8_t value:
             value = reg_data[regnum];
             break;

             case spi_i.set_reg(uint8_t regnum, uint8_t value):
             reg_data[regnum] = value;
             break;
         }
     }
 }
I've included this code into the AN00202 AVB code and try to set SPI registers with sampling frequency and connection status. If you have any input on how to make this, you'll make my day!
Thanks for your help, have a nice week-end,
Vincent
User avatar
mon2
XCore Legend
Posts: 1913
Joined: Thu Jun 10, 2010 11:43 am
Contact:

Post by mon2 »

Change

Code: Select all

#define SPI_REG_COUNT           3
To

Code: Select all

#define SPI_REG_COUNT           5
and test again.
User avatar
mon2
XCore Legend
Posts: 1913
Joined: Thu Jun 10, 2010 11:43 am
Contact:

Post by mon2 »

Your version of the AN00161 code has removed the boundary checks for out of range indexing of the register array:

Here is the original code:

Code: Select all

while (1) {
select {
/*
These cases react to the SPI slave bus. A write from the bus will
update the state of the transaction. A read from the bus will get
sent the data from the currently addressed register.
*/
case i_spi.master_ends_transaction(void):
state = IDLE;
break;
case i_spi.master_requires_data(void) -> uint32_t data:
data = reg_data[addr];
break;

case i_spi.master_supplied_data(uint32_t datum, uint32_t valid_bits):
switch (state) {
case IDLE:
if (datum == WRITE_REG || datum == READ_REG)
state = datum;
break;

case READ_REG:
if (datum < NUM_REG) {     // should include this check
addr = datum;
state = READ_REG_DATA;
} else {
state = IDLE;
}
break;

case READ_REG_DATA:
// Do nothing with master data during a read data operation.
break;

case WRITE_REG:
if (datum < NUM_REG) {  // should include this check
addr = datum;
state = WRITE_REG_DATA;
} else {
state = IDLE;
}
break;

case WRITE_REG_DATA:
reg_data[addr] = datum;
break;
}
break;
mx12
Member++
Posts: 20
Joined: Thu Aug 18, 2016 3:22 pm

Post by mx12 »

mon2 wrote:Change

Code: Select all

#define SPI_REG_COUNT           3
To

Code: Select all

#define SPI_REG_COUNT           5
and test again.
Well, my app only needs 3 registers, so why you suggest to modify that?

I tried an infinite loop writing + reading back the 3 registers from the Blackfin, this seems to work properly with my code. Now, I try to map the registers as following:
0: version (read-only, just the firmware revision)
1: sampling frequency code (0:44,1k, 1:48k, 2:88.2k, ...). Can be read (when AVB is connected to a host), or written (when AVB is not connected)
2: AVB connection status (0: disconnected, 1: connected to a host)

If you have any knowledge on AVB, let me know!
Thanks,
Vincent
User avatar
mon2
XCore Legend
Posts: 1913
Joined: Thu Jun 10, 2010 11:43 am
Contact:

Post by mon2 »

Well, my app only needs 3 registers, so why you suggest to modify that?
Your first post notes that AN00161 is not working correctly and you are working with registers[0]..[4] = 5 registers through the Blackfin code.

However, your XMOS source code is limited to only 3 registers. The XMOS code must also be expanded to 5 registers if you are working with 5 registers.

Not clear if you have additional questions. If you do, best to start a new post with specific details.
Post Reply