The Freedom-K64F is an ultra-low-cost development platform for Kinetis K64, K63, and K24 MCUs.

16-bit SPI support

15 Feb 2016

Hi,

We are testing the FRDM-K64F with a component by STM requiring 16-bit SPI. It seems that the statement

return DSPI_HAL_ReadData(spi_address[obj->instance]) & 0xff;

in function

int spi_master_write(spi_t *obj, int value)

contained in file /code/mbed-dev/file/41a834223ea3/targets/hal/TARGET_Freescale/TARGET_KPSDK_MCUS/spi_api.c does not consider the most significant octet when using SPI at 16-bit.

Could you please confirm and, in such a case, fix the bug?

Thanks and regards Andrea

15 Feb 2016

I can't open the file referenced.

HEre's the file from github, the spi write using KSDK HAL dspi write: https://github.com/mbedmicro/mbed/blob/master/libraries/mbed/targets/hal/TARGET_Freescale/TARGET_KPSDK_MCUS/spi_api.c#L108

What are you seeing ? Can you share the logic analyzer output? Please provide more information

15 Feb 2016

FWIW, here is a low-level example using 16-bit SPI and FIFO to get data rates close to SPI clock speed https://developer.mbed.org/users/manitou/code/k64f_spiperf/

15 Feb 2016

FWIW, here is a low-level example using 16-bit SPI and FIFO to get data rates close to SPI clock speed https://developer.mbed.org/users/manitou/code/k64f_spiperf/

15 Feb 2016

@Martin, issue is a few lines further: https://github.com/mbedmicro/mbed/blob/master/libraries/mbed/targets/hal/TARGET_Freescale/TARGET_KPSDK_MCUS/spi_api.c#L114

There it takes 0xFF of received data, so only 8-bit.

15 Feb 2016

@Erik, I see. The read value returned is the issue here. We shall fix this then

16 Feb 2016

Just a note to the OP: when I was working with a K64F about a year ago, I discovered that the chip appears to drop a byte after a few are transmitted in 16 bit mode. I was sending via DMA and setting it up via register writes, so I don't think it can be the library. It may be why the SD-Card interface doesn't work in 16-bit mode either.

The workaround for me was to just add a byte in the right place and let the chip fail to transmit it. I was only writing and not reading, and as I recall, it was only happening on long continuous transmissions (a few hundred bytes).

Just a "heads up."

17 Feb 2016

Quote:

SD-Card interface doesn't work in 16-bit mode

I know I should probably test this myself, but on some processors, the byte-order is "reversed" if you send a 16-bit word. In my low-level example above, byte pairs are shifted into the SPI register in proper order. Need to confirm how K64F emits a 16-bit frame.

I'd like to see your DMA implementation. thanks

17 Feb 2016

OK, so I cobbled this up from real code, but didn't try it in the compiler. Please forgive me if I missed something, and please don't ask me to debug it for you if it doesn't work. Good luck....

You should get the idea from this at least, especially since it fires off two SPI channels back to back. The trick is that you have to add the SPI command words to each word that goes out, and you have to tell the channel to stop the continuous transmission at the end of each queue. Pesky, but it works.

Enjoy!

- Just Gary

//
// Transmit two SPI buffers at the same time via 16 bit DMA transfer at 12 MBits per second.
// Each buffer is the same size, and both are defined back to back in memory.
// Buffers will transmit each time button PTC6 is pressed.
//
// This example is provided as-is by Just Gary
// Please give credit if you use any part of this example
// Written by Just Gary
// Posted on mbed FRDM-K64F forum February 2016
//

#define BUFLEN   240
#define TRANSMIT BUFLEN*2               // Transmit 480 bytes total
#define TRALEN   TRANSMIT/2             // Transfer length = Transmit bytes / 2 bytes per transfer

SPI        chan0 (PTD2, NC, PTD1);      // SPI0
SPI        chan2 (PTB22, NC, PTB21);    // SPI2
InterruptIn  irqbttn (PTC6);

unsigned long all[TRALEN+1];            // DMA buffer, includes SPI command words
unsigned char xmt_buf[TRANSMIT];        // The buffer full of data to send, half to SPI0 and half to SPI2
unsigned char *str;                     // A work pointer

void dma_init(void) {
    // Enable clock for DMAMUX and DMA
    SIM_SCGC6 |= SIM_SCGC6_DMAMUX_MASK;
    SIM_SCGC7 |= SIM_SCGC7_DMA_MASK;    
            
    // Enable Channel 0 and set SPI0_Tx as DMA request source 
    DMAMUX_CHCFG0 |= DMAMUX_CHCFG_ENBL_MASK | DMAMUX_CHCFG_SOURCE(15);
    // Enable Channel 1 and set SPI2_Tx as DMA request source 
    DMAMUX_CHCFG1 |= DMAMUX_CHCFG_ENBL_MASK | DMAMUX_CHCFG_SOURCE(17);

    // Set memory address for source and destination 
    DMA_TCD0_SADDR = (uint32_t)&all[0];            // First half of buffer
    DMA_TCD0_DADDR = (uint32_t)&SPI0_PUSHR;
    DMA_TCD1_SADDR = (uint32_t)&all[TRALEN/2];     // Second half of buffer
    DMA_TCD1_DADDR = (uint32_t)&SPI2_PUSHR;

    // Set an offset for source and destination address
    DMA_TCD0_SOFF = 0x04; // Source address offset of 4 bytes per transaction
    DMA_TCD0_DOFF = 0x00; // Destination address offset of 0 bytes per transaction
    DMA_TCD1_SOFF = 0x04; // Source address offset of 4 bytes per transaction
    DMA_TCD1_DOFF = 0x00; // Destination address offset of 0 bytes per transaction
        
    // Set source and destination data transfer size
    DMA_TCD0_ATTR = DMA_ATTR_SSIZE(2) | DMA_ATTR_DSIZE(2);
    DMA_TCD1_ATTR = DMA_ATTR_SSIZE(2) | DMA_ATTR_DSIZE(2);
        
    // Number of bytes to be transfered in each service request of the channel
    DMA_TCD0_NBYTES_MLNO = 0x04;
    DMA_TCD1_NBYTES_MLNO = 0x04;
        
    // Current major iteration count (TRALEN iterations of 4 bytes)
    DMA_TCD0_BITER_ELINKNO = DMA_BITER_ELINKNO_BITER(TRALEN/2);
    DMA_TCD0_CITER_ELINKNO = DMA_CITER_ELINKNO_CITER(TRALEN/2);
    DMA_TCD1_BITER_ELINKNO = DMA_BITER_ELINKNO_BITER(TRALEN/2);
    DMA_TCD1_CITER_ELINKNO = DMA_CITER_ELINKNO_CITER(TRALEN/2);
    
    // Adjustment value used to restore the source and destiny address to the initial value
    DMA_TCD0_SLAST = -((TRALEN/2)*4);   // Source address adjustment
    DMA_TCD0_DLASTSGA = 0;              // Destination address adjustment
    DMA_TCD1_SLAST = -((TRALEN/2)*4);   // Source address adjustment
    DMA_TCD1_DLASTSGA = 0;              // Destination address adjustment
    
    // Enable request signal for channel 0 
    DMA_ERQ = DMA_ERQ_ERQ0_MASK | DMA_ERQ_ERQ1_MASK;
        
    // Setup control and status register
    DMA_TCD0_CSR = 0;
    DMA_TCD1_CSR = 0;
}

void send_buffer(void) {
    int i, n;
        
// Prepare the DMA vector
    str = (unsigned char *)&xmt_buf[0];
    for (i=0,n=0; i<TRALEN; i++) {                // 240 transmissions at 2 bytes per transfer
        all[n++] = 0x80000000 | str[2*i]<<8 | str[2*i+1];               // Continuous
    }
    all[TRALEN/2-1] &= 0x0000FFFF;
    all[TRALEN/2-1] |= 0x08000000;                                      // Mark first half end of queue
    all[n-1] = 0x08000000 | str[2*TRALEN-2]<<8 | str[2*TRALEN-1];       // Not continuous, End Of Queue
        
    // Setup a DMA for each SPI bus
    DMA_TCD0_CSR = 0x00000001;
    SPI0_TCR = 0x00000000;  // Clear Transfer Counter
    SPI0_SR  = 0x12000000;  // Start a DMA
    DMA_TCD1_CSR = 0x00000001;
    SPI2_TCR = 0x00000000;  // Clear Transfer Counter
    SPI2_SR  = 0x12000000;  // Start a DMA
}

int main() {
    int i;
    chan0.format (16, 0);
    chan0.frequency (12000000);
    chan2.format (16, 0);
    chan2.frequency (12000000);
    SPI0_RSER  |= 0x03000000;
    SPI2_RSER  |= 0x03000000;

// Load some exciting data into the buffers
    for (i=0; i<BUFLEN; i++) {
        xmt_buf[i] = i;
        xmt_buf[i+BUFLEN] = i;
    }

    dma_init();
    irqbttn.fall(&send_buffer);

    while (1);
}

17 Feb 2016

Thanks for the DMA example. In reading the manual for Teensy 3(Freescale, same IO architecture as K64F) a year or so back, it seemed that it would be ugly to do the DMA. Your example mostly worked (SPI2 drives the mbed k64f LEDs :-). With logic analyzer, SPI clock was already running before button push... whatever. I was able to measure data rate with SPI CLK at 30 MHz, analyzer suggests bit rate of 25.8 megabits/second.

FYI, there is a teensy 3 DMA SPI library that doesn't seem to have to pre-construct a data array for the DMA. see https://github.com/crteensy/DmaSpi

18 Feb 2016

Thanks for the DMA example. In reading the manual for Teensy 3(Freescale, same IO architecture as K64F) a year or so back, it seemed that it would be ugly to do the DMA. Your example mostly worked (SPI2 drives the mbed k64f LEDs :-). With logic analyzer, SPI clock was already running before button push... whatever. I was able to measure data rate with SPI CLK at 30 MHz, analyzer suggests bit rate of 25.8 megabits/second.

FYI, there is a teensy 3 DMA SPI library that doesn't seem to have to pre-construct a data array for the DMA. see https://github.com/crteensy/DmaSpi

18 Feb 2016

It is possible that I munged up the constants or something. I changed them and a few variable names to protect the innocent. The clock should not run without starting a DMA first, but maybe in this example it will only work OK after the first transmission. The first thing to check is the position of the 0x08000000 end-of-queue markers.

Like I said, I didn't try it this way. It is a part of a much larger piece of code. You should have it going in only a few 30-hour debugging sessions.

Don't forget that SPI2 also shows up on J6, the unpopulated RF connector.

19 Feb 2016

tom dunigan wrote:

Thanks for the DMA example. In reading the manual for Teensy 3(Freescale, same IO architecture as K64F) a year or so back, it seemed that it would be ugly to do the DMA. Your example mostly worked (SPI2 drives the mbed k64f LEDs :-). With logic analyzer, SPI clock was already running before button push... whatever. I was able to measure data rate with SPI CLK at 30 MHz, analyzer suggests bit rate of 25.8 megabits/second.

FYI, there is a teensy 3 DMA SPI library that doesn't seem to have to pre-construct a data array for the DMA. see https://github.com/crteensy/DmaSpi

On first glance I don't really see what the DMA lib is doing (everything in the .h file and an empty .cpp file is a bit weird), but in the end the teensy (and K64F and all other similar ones) just simply need to prepare a really irritating data array otherwise they cannot use DMA for their SPI. Other boards (KL25, LPCs, etc) don't have this irritating 'feature'.

19 Feb 2016

Erik - wrote:

On first glance I don't really see what the DMA lib is doing (everything in the .h file and an empty .cpp file is a bit weird), but in the end the teensy (and K64F and all other similar ones) just simply need to prepare a really irritating data array otherwise they cannot use DMA for their SPI. Other boards (KL25, LPCs, etc) don't have this irritating 'feature'.

Erik, the DmaSpi library in fact demonstrates that you do NOT need to build a data array. The library is built on a DMA-channel management layer and utilizes a transaction-based SPI layer, but the 8-bit SPI-DMA logic is evident. The DmaSpi library runs at near SPI-clock speeds (22.3mbs with 30mhz SPI clock).

19 Feb 2016

Just Gary wrote:

Like I said, I didn't try it this way. It is a part of a much larger piece of code. You should have it going in only a few 30-hour debugging sessions.

OK, not quite 30 hours, but I did get good SPI data on the 2nd and subsequent button pushes. The fact that the SPI is free-running after the dma_init() messes up the first button-push data. With the SPI clock at 30MHz and sending 1000 bytes, I get a data rate of 22.5 mbs (megabits/sec). Looking at the logic analyzer, the SPI data clocking is running at 27.4 mbs, but the array building adds 63 us to the transfer time.

19 Feb 2016

tom dunigan wrote:

OK, not quite 30 hours, but I did get good SPI data on the 2nd and subsequent button pushes. The fact that the SPI is free-running after the dma_init() messes up the first button-push data. With the SPI clock at 30MHz and sending 1000 bytes, I get a data rate of 22.5 mbs (megabits/sec). Looking at the logic analyzer, the SPI data clocking is running at 27.4 mbs, but the array building adds 63 us to the transfer time.

Tom -

Since you are are transmitting 1000 bytes and have your logic analyzer hooked up, can you verify my claim that the K64F drops one word in a long transmission? As I recall, it should be the fourth word or so. I tested it by transmitting increasing values (0x00, 0x01, 0x02, etc.) then verifying that two of the bytes were never transmitted. I think I was transmitting 480 bytes (240 16-bit words) at a time.

- Just Gary

19 Feb 2016

Just Gary wrote:

Since you are are transmitting 1000 bytes and have your logic analyzer hooked up, can you verify my claim that the K64F drops one word in a long transmission? As I recall, it should be the fourth word or so. I tested it by transmitting increasing values (0x00, 0x01, 0x02, etc.) then verifying that two of the bytes were never transmitted. I think I was transmitting 480 bytes (240 16-bit words) at a time.

- Just Gary

I'll see what I can see. Too bad your 2nd SPI channel wasn't set up as the receiver, that would make verifying real easy

19 Feb 2016

Sending 1000 bytes or 240 bytes, I don't see any missing bytes in the first 50 or so bytes. and random sampling of the byte stream shows no missing bytes. Though absence of proof is not proof of absence.

If you set up a SPI receive channel in the command-array model, what do you get back from the POPR? 4-byte reply, upper 2 bytes useless, lower 2 bytes the 16-bit frame?

19 Feb 2016

Receiving is a whole new can of worms. I did all of that work a year ago, and it took quite a bit of reading the reference manual and example code to get what I got. I remember thinking that I could take the time to figure out SPI receive, but was glad that I didn't have a need to do so. The manual doesn't say exactly what to expect.

<SOAPBOX> In my experience, NXP Arm manuals are the easiest to read and understand, and all the other manufacturers make it much harder. Now that NXP and Freescale are the same, maybe we'll get more of the good manuals.

I recently began working with the Renesas RZ-A1H, and I have so far been very extremely disappointed in their documentation. It is very hard to understand exactly how to do things, and they offer virtually no decent working examples on how to diddle registers to make things work. </SOAPBOX>

21 Feb 2016

Erik - wrote:

On first glance I don't really see what the DMA lib is doing (everything in the .h file and an empty .cpp file is a bit weird), but in the end the teensy (and K64F and all other similar ones) just simply need to prepare a really irritating data array otherwise they cannot use DMA for their SPI. Other boards (KL25, LPCs, etc) don't have this irritating 'feature'.

For purposes of illustration, I ported the teensy 3 DmaSpi library to an mbed implemenation. see

https://developer.mbed.org/users/manitou/code/k64f_dmaspit/

It does 8-bit DMA SPI send/receive without preparing any arrays. Runs at 22.6mbs with 30MHz SPI clock.

21 Feb 2016

Thanks, I'll guess another time I try to figure out how it actually works (yes something with major/minor links) to work around the SPI limitations.