I was surprised to learn MQX(Ver 4.2) does not support SPI Slave configuration.
There are many workarounds in the community but the information is in bits and pieces. So i decided to write this note.
I wanted to configure SPI0 Channel on MK64FX512VLQ12 board as Slave driven by interrupt in which can call MQX APIs. I have MQX Ver 4.2 installed. My base BSP is derived from TWR-K64 board BSP given by MQX. IDE is KDS.f Reference hardware is TWR-K64 board.
The options available are:
Initially I was hesitant of modifying the BSP. But now it looks to be the best option compared with the other options.
The files needed to be modified for SPI to work as Slave are:
1. user_config.c: I added the following lines here:
/**
* Custom BSP macros.
* */
#define BSPCFG_ENABLE_SPI0_SLAVE 1 //SPI0 as slave.
#define SPI0_SLAVE_POLL 0
/**
* @see NVIC implementation on MQX for the following https://community.nxp.com/docs/DOC-335593
* */
//#define MQX_ROM_VECTORS 1
SPI0_SLAVE_POLL is set to 1 if SPI0 channel polls for data from Master. And set to 0 if interrupt are expected on every event of SPI Master sending data.
MQX_ROM_VECTORS is an MQX macro, define if you wish to have your ISR in the ROM address unlike the BSP registered ISR, which run from RAM. Breifly, this macro setting decide if you want to use _int_install_kernel_isr() or _int_install_isr() to register your ISR. See the following link for more details.
2. init_bsp.c: I added the following lines here:
#if (BSPCFG_ENABLE_SPI0_SLAVE == 1)
spi0_slave_init();
#endif
Based on user_config.h, spi0_slave_init() would be called. Its definition looks like this:
void spi0_slave_init(void)
{
SIM_MemMapPtr sim = SIM_BASE_PTR;
PORT_MemMapPtr pctl;
VDSPI_REG_STRUCT_PTR spi0_base = (void *)SPI0_BASE_PTR;
/* GPIO init */
pctl = (PORT_MemMapPtr)PORTD_BASE_PTR;
/** 2 chipselects
* Select whichever is needed for a Master. */
pctl->PCR[0] = PORT_PCR_MUX(2); /* DSPI0.PCS0 */
pctl->PCR[1] = PORT_PCR_MUX(2); /* DSPI0.SCK */
pctl->PCR[2] = PORT_PCR_MUX(2); /* DSPI0.SOUT */
pctl->PCR[3] = PORT_PCR_MUX(2); /* DSPI0.SIN */
/* Enable clock gate to DSPI0 module */
sim->SCGC6 |= SIM_SCGC6_SPI0_MASK;
/* MCR Configuration */
spi0_base->MCR = 0x00; //Overwrite the Default value of MCR.
spi0_base->MCR |= DSPI_MCR_ROOE_MASK ;
spi0_base->CTAR[0] = DSPI_CTAR_FMSZ(15); //frame size = 16 bits.
spi0_base->RSER &= ~(DSPI_SR_TFFF_MASK); //TFFF flag generates Interrupt requests.
/* Install Interrupts to BSP */
if (SPI0_SLAVE_POLL == 0)
{
spi0_base->RSER |= DSPI_RSER_TCF_RE_MASK; //generate Transmission complete requests.
}
}
This definition is in a new file I added to BSP.
3. vectors.c: Replaced DEFAULT_VECTOR with spi0_slave_isr(my custom ISR) in __vector_table at SPI0_ISR location. It originally looked like this:
DEFAULT_VECTOR, /* 0x2A 0x000000A8 - ivINT_SPI0 */
Now its like this:
spi0_slave_isr, /* 0x2A 0x000000A8 - ivINT_SPI0
This is useful if you are running the ISR from ROM. If you are registering your ISR to MQX, with will be over written. If ISR is not registered to MQX, then MQX APIs(like signalling events or posting semaphore) couldn't be used. So make your choice wisely.
4. <>_ISR.c: This file contains is my custom ISR handler for SPI0 interrupts which occur when data is received on SPI0 channel. This is file is part of my MQX application project and not BSP. The ISR looks as follows:
#if (MQX_ROM_VECTORS == 1) //if bypassing kernel(BSP's) ISRs
void spi0_slave_isr(void)
{
#else
void spi0_slave_isr(void * parameter)
{
(void)parameter; //to satisify compiler.
#endif
static volatile uint8_t * r_buf; //= spi_slave_buffer.r_buf;
static volatile uint8_t * w_buf; // = spi_slave_buffer.w_buf;
static volatile uint8_t len; // = spi_slave_buffer.len_buf;
LED_TOGGLE(&led_blue);
if((spi0_base->SR & DSPI_SR_RFDF_MASK) == DSPI_SR_RFDF_MASK)
{//Read RX FIFO
test_flag |= SPI_RX_ISR;
spi0_base->SR |= DSPI_SR_RFDF_MASK; /* clear the interrupt flag*/
}
else if((spi0_base->SR & DSPI_SR_TCF_MASK) == DSPI_SR_TCF_MASK)
{//Transfer Complete Flag, Write to TX FIFO now
test_flag |= SPI_TX_ISR;
spi0_base->SR |= DSPI_SR_TCF_MASK; /* clear the interrupt flag*/
}
if (_lwevent_set(&lwevent_spi_slave, 0x01) == MQX_OK)
{
LED_TOGGLE(&led_green);
}
}
There is a task which is blocked waiting for the event it looks something like this:
if ((test_flag & SPI_RX_ISR) == SPI_RX_ISR)
{
test_flag &= ~SPI_RX_ISR;
spi_slave_buffer.r_buf[0] = (uint8_t)(DSPI_POPR_RXDATA_GET(spi0_base->POPR) & 0x00FF);
spi_slave_buffer.r_buf[1] = (uint8_t)((DSPI_POPR_RXDATA_GET(spi0_base->POPR) >> 8) & 0x00FF);
printf("\treceived 0x%x 0x%x\n", spi_slave_buffer.r_buf[0], spi_slave_buffer.r_buf[1]);
res = spi_slave_buffer.r_buf[1];
res |= (((uint32_t )spi_slave_buffer.r_buf[0]) << 8);
spi0_base->SR |= DSPI_SR_TCF_MASK; //clear the TCF Flag.
spi0_base->PUSHR = res; //write the data
}
else if((test_flag & SPI_TX_ISR) == SPI_TX_ISR)
{
test_flag &= ~SPI_TX_ISR;
res = 0;
while (spi_slave_buffer.len_buf > res)
{
spi0_base->PUSHR = spi_slave_buffer.w_buf[res];
res++;
}
printf("\ttransmission complete\n");
}
else
{
printf("\tSomething's wrong with SPI0_Slave!!\n");
}
I have tried this ISR be registering it to MQX as well as running it from ROM directly. If you want to run the ISR from ROM table you need to remove MQX APIs and ensure proper synchronization between ISR and the tasks(this is removed here for brevity).
With this, you are ready to go! To conclude, modifying the BSP for an interrupt driven SPI Slave driver is easier than any other option. Also there is no other documented description available. I had tried the other 3 options I mentioned at the beginning of this note. But ended up in loosing lot of time and no logical conclusion. Its unfortunate that MQX has not closed this issue which has been existing and known to them for so long.
Hope this helps.
PS: I have removed some of the lines for brevity and to keep the note simple. Don't panic!
Happy hacking!
Sarma
You must be a registered user to add a comment. If you've already registered, sign in. Otherwise, register and sign in.