Author Topic: Trouble getting SPI working on dsPIC33EV256GM106  (Read 2848 times)

0 Members and 1 Guest are viewing this topic.

Offline tech_builderTopic starter

  • Contributor
  • Posts: 46
  • Country: ca
Trouble getting SPI working on dsPIC33EV256GM106
« on: April 09, 2018, 11:26:14 pm »
Hi,
 
I am struggling to get the SPI module on a dsPIC33EV256GM106. It is the dsPIC33EV 5V CAN-LIN Starter Kit specifically that I am using. An overview of my task is to read via SPI an ADS1118 ADC and place the reading onto the CAN interface. The demo code that ships with the kit does a great job of interfacing with CAN (so far). However, I cannot seem to get the SPI module functioning. My test code is a pared down version of the demo code in which I inserted SPI functionality. I have tried a few different initialization procedures that were floating around the forums as well as using the MCC configuration tool and copying some of that code into mine.
 
Below is some of the code. In the initSPI() function you will find my SPI initialization code. There lie the trials and tribulations of my efforts in the form of commented out code. The first code commented out is because initially I wanted to use SPI2 because SPI1 is fixed pin. However, I was unclear about the PPS functionality for this chip and didn't want to add to the burden of just getting the peripheral working (it seems from the demo code that there is no unlock procedure for PPS??). The uncommented code is example code from the datasheets, and the last bit of commented out code in initSPI() is code generated from the MCC tool.
 
When I run this code in the debugger it appears as though the SPI registers are being set correctly, but when I run it there is no output on any of the output pins (I have a logic analyzer and scope on them). I have initialized the SPI pins as inputs and outputs as needed and turned off analog functionality. In my initADS1118() function I am writing to the SPI port continuously just to have something to see for functionality. Eventually this will develop into setting up the ADS1118 properly for use.
 
Does anybody have any idea what I am doing incorrectly? Did I forget to dot an i? I did not think that setting up an SPI module would be so difficult, but it seems that this particular version of dsPIC has a lot of 'enhanced' functionality and I'm not too sure how to weed through what I need and don't need.
 
Any help would be greatly appreciated.
 
Thanks!

In the below code I am setting defines for the pins being used for the SPI module.
Code: [Select]
/* for SPI2
#define TRISSCLK _TRISB8
#define ANSEL_SCLK _ANSB8
#define TRISMISO _TRISB9
#define ANSEL_MISO _ANSB9
#define TRISMOSI _TRISB10
#define TRISCS _TRISB11
*/
// for SPI1
#define TRISSCLK _TRISC3
#define ANSEL_SCLK _ANSC3
#define TRISMISO _TRISA9
#define ANSEL_MISO _ANSA9
#define TRISMOSI _TRISA4
#define TRISCS _TRISB0

This is the main function, it just calls the initialization functions.
Code: [Select]
int main(void)
{

    // Configure Oscillator Clock Source
    oscConfig();

    // Clear Interrupt Flags
    clearIntrflags();

    // Initialize hardware on the board
    init_hw();

    // Test to see if we are in TRANSMIT or RECIEVE mode for the demo, show LEDs
    Test_Mode();

    if (mode == TRANSMIT)
    {
        LED_Transmit();
    }
    else
    {
        LED_Receive();
        while ((SW1 == 0) | (SW2 == 0) | (SW3 == 0)); // wait to release all keys
        LED1 = 0; // initialize LEDs to all off
        LED2 = 0;
        LED3 = 0;
    }
    // Initialize the ADC converter
    ADCInit();

    // Initialize the CAN module
    InitCAN();
   
    // initialize the SPI module
    initSPI();
    // initialize the ADS1118 ADC
    initADS1118();

    // main loop: every 4 Timer1 ticks (1sec), scan the sensors and transmit the data
    // or wait for a Receive interrupt from 1 of the 3 interfaces
    //
    s_tick = 0;
    while (1)
    {
        if (mode == RECEIVE)
        {
     /* check to see when a message is received and move the message
  into RAM and parse the message */
  if(canRxMessage.buffer_status == CAN_BUF_FULL)
  {
   rxECAN(&canRxMessage);

   /* reset the flag when done */
   canRxMessage.buffer_status = CAN_BUF_EMPTY;
        }
       
            Receive_Data();
            s_tick = 0;
        }
        else if (mode == TRANSMIT)
        {
 //
 // wait for the 250ms timer. User can accumulate s_ticks to get longer delays
 //
            while (s_tick <= 1); // wait for 0.5 seconds
            s_tick = 0; // clear flag
            Transmit_Data();

        }
    }
}

Here is where I set the TRIS values for input/output status of the pins as well as turn off any analog functionality on the pins.
Code: [Select]
void init_hw(void)
{
    // set up the LED and switch ports

    TRISLED1 = 0;
    TRISLED2 = 0;
    TRISLED3 = 0;
    ANSEL_POT = 1;
    ANSEL_TEMP = 1;
    TRIS_POT = 1;
    TRIS_TEMP = 1;
   
    ANSELA = 0; // make port A all digital I/O
    ANSELB = 0; // make port B all digital I/O
    ANSELC = 0; // make port C all digital I/O
    TRISSCLK = 0; // make pin an output
    ANSEL_SCLK = 0; // make it a digital pin
    TRISMISO = 1; // make pin an input
    ANSEL_MISO = 0; // make it a digital pin
    TRISMOSI = 0; // make pin an output
    TRISCS = 0; // make pin an output
   
    ANSELC = ANSELC & 0xFC3F; // (re)set the 3 switch bits + CAN due to error in v1.20 header
    _ANSC3 = 0;
   
    s_tick = 0;
    f_tick = 0; // the timer ticks

    //
    // Timer 1 to generate an interrupt every 250ms
    //
    T1CONbits.TON = 0; // Disable Timer1
    T1CONbits.TCS = 0; // Select internal instruction cycle clock
    T1CONbits.TGATE = 0; // Disable Gated Timer mode
    T1CONbits.TCKPS = 0x3; // Select 1:256 Prescaler
    PR1 = 39062; // Load the period value (250ms/(256*25ns))
    IPC0bits.T1IP = 0x03; // Set Timer 1 Interrupt Priority Level
    IFS0bits.T1IF = 0; // Clear Timer 1 Interrupt Flag
    IEC0bits.T1IE = 1; // Enable Timer1 interrupt

    //
    // Timer 2 to generate an interrupt every 10ms
    //
    T2CONbits.TON = 0; // Disable Timer2
    T2CONbits.TCS = 0; // Select internal instruction cycle clock
    T2CONbits.TGATE = 0; // Disable Gated Timer mode
    T2CONbits.TCKPS = 0x3; // Select 1:256 Prescaler
    TMR2 = 0x00; // Clear timer register
    PR2 = 1562; // Load the period value (10ms/(256*25ns))
    IPC1bits.T2IP = 0x02; // Set Timer 2 Interrupt Priority Level
    IFS0bits.T2IF = 0; // Clear Timer 2 Interrupt Flag
    IEC0bits.T2IE = 1; // Enable Timer2 interrupt

    T2CONbits.TON = 1; // Start Timer2
    T1CONbits.TON = 1; // Start Timer1
}

This is where the SPI port is initialized.
Code: [Select]
void initSPI(void)
{
   
    // Peripheral pin select for the SPI2 module
    // see section 11 in datasheet on I/O config
    //RPOR3 = 0x09; // connect SCK2 to RP40
    //RPOR4 = 0x0A08; // connect SDO2 to RP42 and CS to RP43
    //RPINR22 = 0x29; // connect SDI to RP41
   
    /* The following code sequence shows SPI register configuration for Master mode */
    IFS0bits.SPI1IF = 0; // Clear the Interrupt flag
    IEC0bits.SPI1IE = 0; // Disable the interrupt
    // SPI1CON1 Register Settings
    SPI1CON1bits.DISSCK = 0; // Internal serial clock is enabled
    SPI1CON1bits.DISSDO = 0; // SDOx pin is controlled by the module
    SPI1CON1bits.MODE16 = 0; // Communication is word-wide (16 bits)
    SPI1CON1bits.MSTEN = 1; // Master mode enabled
    SPI1CON1bits.SMP = 0; // Input data is sampled at the middle of data output time
    SPI1CON1bits.CKE = 0; // Serial output data changes on transition from
    // Idle clock state to active clock state
    SPI1CON1bits.CKP = 0; // Idle state for clock is a low level;
    // active state is a high level
    SPI1STATbits.SPIEN = 1; // Enable SPI module
    // Interrupt Controller Settings
    IFS0bits.SPI1IF = 0; // Clear the Interrupt flag
    //IEC0bits.SPI1IE = 1; // Enable the interrupt
   
    // MSTEN Master; DISSDO disabled; PPRE 64:1; SPRE 8:1; MODE16 disabled; SMP Middle; DISSCK disabled; CKP Idle:Low, Active:High; CKE Idle to Active; SSEN disabled;
    //SPI1CON1 = 0x20;
    // SPIFSD disabled; SPIBEN disabled; FRMPOL disabled; FRMDLY disabled; FRMEN disabled;
    //SPI1CON2 = 0x0;
    // SISEL SPI_INT_SPIRBF; SPIROV disabled; SPIEN enabled; SPISIDL disabled;
    //SPI1STAT = 0x8000;
}

This is where the magic isn't happening. I feel like this should be straight forward, and several example pieces of code are similar to this that seem to work for other people. What might I be missing here? Or is the problem in the configuration?
Code: [Select]
void initADS1118(void)
{
    while(1){
    while( SPI1STATbits.SPITBF);
    SPI1BUF = 0x83;
    while(!SPI1STATbits.SPIRBF);
    }
    //SPI2_Exchange8bit(0x83);
    //SPI2_Exchange8bit(0x8B);
}

 

Online SiliconWizard

  • Super Contributor
  • ***
  • Posts: 14299
  • Country: fr
Re: Trouble getting SPI working on dsPIC33EV256GM106
« Reply #1 on: April 11, 2018, 12:30:51 am »
Apparently, this part supports PPS (peripheral pin select). But I see no initialization of PPS for your SPI pins.
There's an attempt in your initSPI() function, but it's commented out.

If I remember well, I/O pins are simple digital I/O's by default, so the right PPS settings are needed for peripheral functions and are not optional if I'm not mistaken.
 

Offline JPortici

  • Super Contributor
  • ***
  • Posts: 3452
  • Country: it
Re: Trouble getting SPI working on dsPIC33EV256GM106
« Reply #2 on: April 11, 2018, 05:41:00 am »
Correct, that is exactly what paragraph 11.5.2 of datasheet says: in dsPICs there is no default pin assignment.
note that for the clock, as it can be both an input and an output, you have to assign it both on _SCK2R (input) and _RPxR (output)

this is one of the parts i use the most, if i have time later i will post the configuration code.

I did have weird issues when i first used it with DMA (unexpected waveforms, like seeing a long capacitor discharge at times, as if it changed the output line to high impedance at times..) so in the end i used the enhanced mode, 8 buffers. as it was to read/write a memory, i didn't require particular high speed. firing the interrupt every 8 bytes was acceptable, then i never changed the code because it worked well..
same with DACs, required samplerates were never high so i could use interrupts

EDIT:
SPI1 is'nt PPS-able, in that case this code works for me

Code: [Select]
/* SPI */
_TRISA4 = 0;  //SDO1
_ANSA4 = 0;
_TRISA9 = 1;  //SDI1
_ANSA9 = 0;
_TRISC3 = 0;  //SCK1
_ANSC3 = 0;
_CS1 = 1;
_TRISB4 = 0;  //CS

Code: [Select]
void SPI1_Init() {
  SPI1STAT = 0;
  SPI1CON1bits.DISSCK = 0;  //Internal SPI Clock Enabled
  SPI1CON1bits.DISSDO = 0;  //SDO Enabled
  SPI1CON1bits.SSEN = 0;    //_SS is not used
  SPI1CON1bits.MSTEN = 1;   //Master Mode
  SPI1CON1bits.MODE16 = 0;  //8 bit transfer
  SPI1CON1bits.SMP = 0;     //Data Sampled at middle of time
  SPI1CON1bits.CKE = 1;     //Mode 0
  SPI1CON1bits.CKP = 0;
 
  SPI1CON1bits.SPRE = 4;    //1:4
  SPI1CON1bits.PPRE = 1;    //1:16 -> 500 kHz SPI clock
 
  SPI1CON2 = 1;             //Disable Framed mode, Enable enhanced mode
 
  SPI1STATbits.SPIEN = 1;
 
  _SPI1IF = 0;
}

same should work with SPI2 if you assign the pins (SDI and SCK PPS input, SDO and SCK PPS output)

other notes:
-it's not uncommon to have issues or erratas on the SPI regarding the status bits, that's why i tend to use interrupts or clear then check the interrupt flag anyway.
-check the errata, but the remaining SPI issues should not affect you
-clock prescalers can't be both set at 1:1 at the same time, in that case the peripheral won't work.
-after a transaction you must read back the SPIxBUF register, otherwise it will stay in the FIFO buffer and after one or more transactions (depending on enchanced mode status) it will overflow and the peripheral will stop working. relevant status bit should be set.
-this is my goto function for spi
Code: [Select]
uint16_t SPI1_Send(uint16_t data) {
  /* you may or may not want to change the size of data and return depending on the data width and MCU */
  _SPI1IF = 0;
  SPI1BUF = data;
  while(!_SPI1IF);
  return SPI1BUF;
}

or, when the status bits do work
Code: [Select]
uint16_t SPI1_Send(uint16_t data) {
  while(!SPI1STATbits.SRMPT);  //Wait for a possible previous transaction to end
  SPI1BUF = data;
  while(!SPI1STATbits.SRMPT);
  return SPI1BUF;
}
« Last Edit: April 11, 2018, 07:42:10 am by JPortici »
 

Offline tech_builderTopic starter

  • Contributor
  • Posts: 46
  • Country: ca
Re: Trouble getting SPI working on dsPIC33EV256GM106
« Reply #3 on: April 14, 2018, 06:42:43 am »
Thanks so much for the replys. I will take a look and hopefully it will work.

One of the things that confused me about the demo code that goes with the dsPIC33EV 5V CAN-LIN Starter Kit was that the PPS for the CAN peripheral didn't use the unlock/lock sequence, and because I couldn't figure out why I chose to use SPI1 on the fixed pins. Do you have any idea how they are getting away without using the unlock/lock sequence? The code below is the relevant part of the CAN initialization function.

Code: [Select]
//
    // drive the CAN STANDBY driver pin low
    //
    _TRISG9 = 0;
    _LATG9 = 0;
    _TRISF1 = 0;
    _TRISF0 = 1;

    //
    // remap the CAN module to the proper pins on the board
    //
    RPINR26 = 0x60;         // connect CAN RX to RPI96
    RPOR9 = 0x000E;         // connect CAN TX to RP97
   
    C1CTRL1bits.REQOP = 4;

    while (C1CTRL1bits.OPMODE != 4);
    C1CTRL1bits.WIN = 0;
 

Offline JPortici

  • Super Contributor
  • ***
  • Posts: 3452
  • Country: it
Re: Trouble getting SPI working on dsPIC33EV256GM106
« Reply #4 on: April 14, 2018, 07:25:16 am »
No need to unlock because PPS is unlocked at reset.

After the configuration, if you want, you may lock the PPS and then if the configuration bit lets you, you can unlock the PPS at any moment
 

Online SiliconWizard

  • Super Contributor
  • ***
  • Posts: 14299
  • Country: fr
Re: Trouble getting SPI working on dsPIC33EV256GM106
« Reply #5 on: April 14, 2018, 02:13:18 pm »
No need to unlock because PPS is unlocked at reset.

After the configuration, if you want, you may lock the PPS and then if the configuration bit lets you, you can unlock the PPS at any moment

It's recommended to lock the configuration after you set it to make sure there can't be any unwanted change due to software bugs - which could be potentially dangerous since a bad pin config could fry your µC.

If you develop in C, use __builtin_write_OSCCONL() and __builtin_write_OSCCONH() to lock/unlock to make sure the proper sequences are generated.
(See section 10.4.4.1)
 


Share me

Digg  Facebook  SlashDot  Delicious  Technorati  Twitter  Google  Yahoo
Smf