SPI format length

I’ve been using the SPI peripheral for a few odds and sods, but I’m now encountering a slave device which requires 12-bit long data frames and I’m not entirely clear how that should be done. I set up my SPI registers:

SPI_REG(SPI_REG_FMT) =
    SPI_FMT_PROTO(SPI_PROTO_S)     |
    SPI_FMT_ENDIAN(SPI_ENDIAN_MSB) |
    SPI_FMT_DIR(SPI_DIR_TX)        |
    SPI_FMT_LEN(12); // 12 bit long packets
SPI_REG(SPI_REG_CSMODE) = SPI_CSMODE_AUTO;

and then write data out:

void write_SPI(uint8_t data)
{
    while (SPI_REG(SPI_REG_TXFIFO) & SPI_TXFIFO_FULL);
    SPI_REG(SPI_REG_TXFIFO) = data;
}

How do I send out the remaining nibble? If I try writing to the FIFO again it triggers a new frame automatically so I see my data byte as part of one 12-bit frame (padded with 1s) for the first frame, followed by a second frame with my nibble.

Cheers.

For the Freedom E310, the maximum SPI frame length is 8 (although the Chisel RTL is parameterized to support an arbitrary length).

However, it is possible to emulate a 12-bit transfer using two consecutive 8-bit “frames”. Assuming that the slave is able to tolerate the extra 4 data bits:

SPI_REG(SPI_REG_FMT) =
    SPI_FMT_PROTO(SPI_PROTO_S)     |
    SPI_FMT_ENDIAN(SPI_ENDIAN_MSB) |
    SPI_FMT_DIR(SPI_DIR_TX)        |
    SPI_FMT_LEN(8);

static void tx(uint8_t data)
{
    while (SPI_REG(SPI_REG_TXFIFO) & SPI_TXFIFO_FULL);
    SPI_REG(SPI_REG_TXFIFO) = data;
}

void write_SPI(uint16_t data)
{
    SPI_REG(SPI_REG_CSMODE) = SPI_CSMODE_HOLD; // assert CS
    tx(data >> 4); // send upper 8 bits
    tx(data << 4); // send lower 4 bits (right-aligned when using MSB-first order)
    SPI_REG(SPI_REG_CSMODE) = SPI_CSMODE_AUTO; // deassert CS
}

Otherwise, to send exactly 12 bits, use an 8-bit frame followed by a 4-bit frame. Note that it is necessary to wait for all ongoing transfers to complete before changing the frame length.

// disable watermark interrupts (default on reset)
SPI_REG(SPI_REG_IE) = 0;
// set watermark to detect empty TXFIFO (default on reset)
SPI_REG(SPI_REG_TXCTRL) = SPI_TXWM(0);

static void fmt(unsigned int len)
{
    SPI_REG(SPI_REG_FMT) =
        SPI_FMT_PROTO(SPI_PROTO_S)     |
        SPI_FMT_ENDIAN(SPI_ENDIAN_MSB) |
        SPI_FMT_DIR(SPI_DIR_TX)        |
        SPI_FMT_LEN(len);
}

void write_SPI(uint16_t data)
{
    SPI_REG(SPI_REG_CSMODE) = SPI_CSMODE_HOLD; // assert CS

    while (!(SPI_REG(SPI_REG_IP) & SPI_IP_TXWM)); // wait for TXFIFO to drain
    fmt(8);        // set frame length to 8
    tx(data >> 4); // send upper 8 bits

    while (!(SPI_REG(SPI_REG_IP) & SPI_IP_TXWM)); // wait for TXFIFO to drain
    fmt(4);        // set frame length to 4
    tx(data << 4); // send lower 4 bits (right-aligned when using MSB-first order)

    SPI_REG(SPI_REG_CSMODE) = SPI_CSMODE_AUTO; // deassert CS
}
1 Like

Thanks for the feedback and code samples. I gave it a try and switching format helps in that it does reduce the MOSI output to the desired 12 bits. However, even with using CSMODE as above it still de-asserts the CS in between frames (see attached capture). I had to leave the office before I had a chance to try, but I’ll try and control that pin manually via GPIO_REG instead.

Thanks.

1 Like