I’m currently working on SPI communication between two microcontrollers. The MOSI frames are working as expected; however, I’m facing an issue on the MISO line. It appears that the end of the frame is getting corrupted or overlapping.
Here, I’m sharing the data captured on the MOSI and MISO lines for reference.
7B 69 19 00 00 69 19 00 00 00 01 00 00 00 00 07 00 00 00 00 00 00 00 00 00 00 00
In the MISO data shown above, you can see that there is a data overlap. When comparing it with the previous data, bytes 1 and 2 are repeating, which indicates frame corruption. This issue occurs very frequently and is causing data loss.
I am using DMA for both receiving and transmitting the SPI data. Could you please help me understand what might be causing this issue and how I can fix it? Also, which direction should I focus on while debugging this problem?
Below, I’m sharing the SPI configuration of the slave (MISO) and the microcontrollers used.
Mater Microcontroller : MKV42F256VLH16
Slave Microcontroller : dsPIC33EP512MU810
Master configuration :
CLock speed: 1.75MHz
MSB first
CPOL = 0
CPHA = 1
Enable -> Active low
#define SPI_IN_BUFFER_SIZE 27
#define SPI_OUT_BUFFER_SIZE 27
volatile uint8_t spi_in_buffer[SPI_IN_BUFFER_SIZE];
volatile uint8_t spi_out_buffer[SPI_OUT_BUFFER_SIZE];
/*Slave SPI configuration code dsPIC33EP512MU810*/
extern void SPI_MC_Init(void)
{
/* reset buffer */
memset((void*) spi_in_buffer, 0x00, SPI_IN_BUFFER_SIZE);
memset((void*) spi_out_buffer, 0x00, SPI_OUT_BUFFER_SIZE);
/* -------------------------- SPI --------------------------------------- */
/* setup chip select */
//DD_SPI_CS_OUT = 1; /* deselect SPI device */
DD_SPI_MISO_TRIS = 1;
DD_SPI_CS_TRIS = 1; /* set SPI nSS as input */
DD_SPI_CLK_TRIS = 1; /* set SPI CLK as input */
DD_SPI_MOSI_TRIS = 1; /* set SPI MOSI as input */
DD_SPI_MOSI_PULL_UP = 1; /*enable pullup, since Motor driver controller MOSI need pullup*/
DD_SPI_MISO_TRIS = 0; /* set SPI MISO as output */
/* SPI2 status register */
MC_SPI_STATBITS.SPIEN = 0; /* 0 = SPIx module is disabled */
MC_SPI_STATBITS.SPISIDL = 0; /* 0 = SPIx module operation is continued in Idle mode */
MC_SPI_STATBITS.SPIBEC = 0b000; /* Number of SPIx transfers are pending. */
MC_SPI_STATBITS.SRMPT = 0; /* 0 = The SPIx Shift register is not empty */
MC_SPI_STATBITS.SRXMPT = 0; /* 0 = RX FIFO is not empty */
MC_SPI_STATBITS.SISEL = 0b000; /* Interrupt when the last data bit in the receive buffer is read, and as a result, the buffer is empty (the SRXMPT bit is set) */
MC_SPI_STATBITS.SPIROV = 0; /* No Receive Overflow has occurred */
/* SPI2 Control Register 1 */
MC_SPI_CON1BITS.DISSCK = 0; /* 0 = SPIx clock on SCKx pin is enabled */
MC_SPI_CON1BITS.DISSDO = 0; /* 0 = SDOx pin is controlled by the module */
MC_SPI_CON1BITS.MODE16 = 0; /* 0 = Communication is byte-wide (8 bits) */
MC_SPI_CON1BITS.SMP = 0; /* 0 = Input data is sampled at the middle of data output time */
MC_SPI_CON1BITS.CKE = 0; /* 0 = Serial output data changes on transition from idle clock state to active clock state (see bit 6) */
MC_SPI_CON1BITS.SSEN = 1; /* 0 = SSx pin is not used by the module; pin is controlled by port function */
MC_SPI_CON1BITS.CKP = 0; /* 0 = Idle state for clock is a low level; active state is a high level */
MC_SPI_CON1BITS.MSTEN = 0; /* 0 = Slave mode */
//SPI2CON1bits.SPRE = 0b100; /* 100 = Secondary prescale 4:1 */
//SPI2CON1bits.PPRE = 0b01;//0b00; /* Primary prescale 64:1 */
/* SPI2 Control Register 2 */
MC_SPI_CON2BITS.FRMEN = 0; /* 0 = Framed SPIx support is disabled */
MC_SPI_CON2BITS.SPIFSD = 0; /* 0 = Frame sync pulse output (master) */
MC_SPI_CON2BITS.FRMPOL = 0; /* 0 = Frame sync pulse is active-low */
MC_SPI_CON2BITS.FRMDLY = 0; /* 0 = Frame sync pulse precedes the first bit clock */
MC_SPI_CON2BITS.SPIBEN = 0; /* 0 = Enhanced buffer is disabled (Legacy mode) */
/* -------------------------- DMA Tx ------------------------------------ */
/* setup DMA8 for writing to peripheral */
DMA8CONbits.CHEN = 0; /* Channel disabled */
DMA8CONbits.SIZE = 1; /* Byte */
DMA8CONbits.DIR = 1; /* Read from DPSRAM (or RAM) address, write to peripheral address */
DMA8CONbits.HALF = 0; /* Initiate interrupt whenn all of the data has been moved */
DMA8CONbits.NULLW = 0; /* Normal operation */
DMA8CONbits.AMODE = 0b00; /* Register Indirect with Post-Increment mode */
DMA8CONbits.MODE = 0b00; /* Continuous, Ping-Pong modes disabled */
/* DMA Channel IRQ Select Register */
DMA8REQbits.FORCE = 0; /* Automatic DMA transfer initation by DMA Request */
DMA8REQbits.IRQSEL = MC_SPI_IRQSEL; /* SPI1 Transfer Done */
/* setup DMA buffer address */
DMA8STAL = (volatile uint16_t)spi_out_buffer;
DMA8STAH = 0;
/* setup peripheral address */
DMA8PAD = (volatile uint16_t)&MC_SPI_BUF;
/* setup DMA transfer length */
DMA8CNT = SPI_OUT_BUFFER_SIZE-1;
/* -------------------------- DMA Rx ------------------------------------ */
/* setup DMA9 for reading frim peripheral */
DMA9CONbits.CHEN = 0; /* Channel disabled */
DMA9CONbits.SIZE = 1; /* Byte */
DMA9CONbits.DIR = 0; /* Read from Peripheral address, write to DPSRAM (or RAM) address */
DMA9CONbits.HALF = 0; /* Initiate interrupt whenn all of the data has been moved */
DMA9CONbits.NULLW = 0; /* Normal operation */
DMA9CONbits.AMODE = 0b00; /* Register Indirect with Post-Increment mode */
DMA9CONbits.MODE = 0b00; /* Continuous, Ping-Pong modes disabled */
/* DMA Channel IRQ Select Register */
DMA9REQbits.FORCE = 0; // Automatic DMA transfer initation by DMA Request
DMA9REQbits.IRQSEL = MC_SPI_IRQSEL; // SPI1 Transfer Done
/* setup DMA buffer address */
DMA9STAL = (volatile uint16_t)spi_in_buffer;
DMA9STAH = 0;
/* setup peripheral address */
DMA9PAD = (volatile uint16_t)&MC_SPI_BUF;
/* setup DMA transfer length */
DMA9CNT = SPI_IN_BUFFER_SIZE-1;
/* ---------------------------------------------------------------------- */
/* setup interrupt priority */
MC_SPI_IP = 7;
MC_SPI_EIP = 7;
IPC29bits.DMA8IP = 7;
IPC29bits.DMA9IP = 7;
/* clear interrupt flags */
MC_SPI_IF = 0;
MC_SPI_EIF = 0;
IFS7bits.DMA8IF = 0;
IFS7bits.DMA9IF = 0;
/* enable interrupts */
MC_SPI_IE = 1;
// IEC0bits.SPI1EIE = 1; /*error handler interrupt will get enable as soon as the first valid message is received*/
IEC7bits.DMA8IE = 1;
IEC7bits.DMA9IE = 1;
return;
}
/*Slave receive Interrupt*/
void __attribute__((interrupt, auto_psv)) _DMA9Interrupt(void)
{
/* Copy current state of RX-Buffer in inbuffer */
mystruct_in inbuffer;
memcpy(&inbuffer, &spi_in_buffer, SPI_IN_BUFFER_SIZE);
static bool lu1_error_handling_enabled = false;
static uint8_t lu8_error_count = 0;
/* check frame marks */
if( ('{' == inbuffer.u8_SOF) && ('}' == inbuffer.u8_EOF) )
{
/* check CRC - without frame marks and crc field */
if( inbuffer.u16Crc == calculatecrc((uint8_t *)&spi_in_buffer[1], SPI_IN_BUFFER_SIZE-4) )
{
if(false == lu1_error_handling_enabled)
{
lu1_error_handling_enabled = true;
MC_SPI_EIF = 0;
MC_SPI_EIE = 1;
}
/* update current data struct */
lu8_error_count = 0;
/*renew SPI timout. when it runs out SPI module will be reset.*/
spi_error_timeout = 1000 + Timer_GetTickCount();
}
else /* crc wrong - out of sync? */
{
//TOGGLE_PIN ^=1;
DMA9CONbits.CHEN = 0;
DMA9CONbits.CHEN = 1;
}
}
else /* out of sync */
{
DMA9CONbits.CHEN = 0;
DMA9CONbits.CHEN = 1;
//TOGGLE_PIN ^=1;
/*count if SPI is out of sync*/
lu8_error_count++;
}
//TOGGLE_PIN ^=1;
//DMA9CONbits.CHEN = 0; /* disable DMA */
IFS7bits.DMA9IF = 0; /* clear interrupt flag */
//DMA9CONbits.CHEN = 1;
/* update Tx data */
SetSPIOutData(&spi_out_buffer[0]);
/* calc crc - without frame marks and crc field */
((mystruct_out)spi_out_buffer)->u16Crc = calculatecrc((uint8_t *)&spi_out_buffer[1], SPI_OUT_BUFFER_SIZE-4);
/* reset Tx DMA Chan. */
DMA8CONbits.CHEN = 0;
DMA8CONbits.CHEN = 1;
DMA8REQbits.FORCE = 1;
if(lu8_error_count >100)
{
lu8_error_count = 0;
/*if SPI is out of sync, reset SPI*/
SPI_MC_Enable();
}
}
/*Initializing SPI again after some error count*/
extern void SPI_MC_Enable(void)
{
SPI_MC_Init();
MC_SPI_STATBITS.SPIEN = 1; // enable SPI2 module
/* enable DMA channels */
DMA8CONbits.CHEN = 1;
DMA9CONbits.CHEN = 1;
/* Force First Byte to enabling new SPI transfer */
DMA8REQbits.FORCE = 1;
return;
}
/*Slave code : MISO data update, unfortunately I could not share entier code */
extern void SetSPIOutData(volatile uint8_t *ap8_outBufferRef)
{
mystruct_out lpt_OutData = (mystruct_out)ap8_outBufferRef;
lpt_OutData->u8_SOF = '{';
/* update all other structure fields I'm restricted to show all codes */
lpt_OutData->u8_EOF = '}';
return;
}
The master is trying to communicate with slave every 375us. This means the slave should prepare all of its data and make it ready for master. Unfortunately, there is a lot of work going on the slave micro-controller, so it is struggling to prepare the data. Given this, I just extended the interval from 375us to 750us and now I couldn't see any frame losses on MISO line. Here is the code:
/* trigger a SPI transfer each 375us - 6x 62.5us */
if( 12 <= lu8_spiTrigger++ )
{
spi_TriggerTransfer(HAL_SPI_TX_BUF_SIZE);
lu8_spiTrigger = 0;
}
Also, I didn’t check for SPI overrun errors. Adding that check might indeed save a lot of debugging time!