SAMD21 SPI configuration without ASF

522 Views Asked by At

I'm currently working on a project optimization based on SAMD21J18A µC. My code is working but it's based on ASF API an I'm trying to gradually get rid of it.

I managed to do it for most of my code but I'm experiencing difficulties when I want to configure the SPI bus without ASF.

My goal is to communicate through SPI with a shift register to control LEDs.

My code with ASF :

void configure_sercom0_spi(struct spi_module *const spi_master_instance, struct spi_slave_inst *const slave){
    struct spi_config master_config;
    struct spi_slave_inst_config slave_config;
    

    spi_get_config_defaults(&master_config);
    
    master_config.mux_setting = SPI_SIGNAL_MUX_SETTING_E;  // DOPO: 0x1, DIPO: 0x0
    master_config.pinmux_pad0 = PINMUX_PA04D_SERCOM0_PAD0; // MISO PA04
    master_config.pinmux_pad1 = PINMUX_PA05D_SERCOM0_PAD1; // Slave Selection PA05
    master_config.pinmux_pad2 = PINMUX_PA06D_SERCOM0_PAD2; // MOSI PA06
    master_config.pinmux_pad3 = PINMUX_PA07D_SERCOM0_PAD3; // SCK PA07

    master_config.mode_specific.master.baudrate = 0xF4240; // 1000000
    
    spi_slave_inst_get_config_defaults(&slave_config);
    slave_config.ss_pin = PIN_PA05;
    
    spi_init(spi_master_instance, SERCOM0, &master_config);
    spi_attach_slave(slave, &slave_config);
    
    spi_enable(spi_master_instance);
}

My code without ASF (EDIT) :

#define SPI_LIGHTING_MAIN_CLK_FREQ    0x7A1200  // 8Mhz
#define SPI_LIGHTING_BAUDRATE         0xF4240   // 1000000

// Peripheral function D selected
#define SPI_LIGHTING_PERIPHERAL_MUX_EVEN  0x3
#define SPI_LIGHTING_PERIPHERAL_MUX_ODD   0x3

void gclk_spi_config(void){
    // GCLK generator 0 ; No division
    GCLK->GENDIV.reg            |= GCLK_GENDIV_ID(0)
                                | GCLK_GENDIV_DIV(1);
    
    // Generic generator 0 ; OSC8M oscillator
    GCLK->GENCTRL.reg           |= GCLK_GENCTRL_ID(0)
                                | GCLK_GENCTRL_GENEN
                                | GCLK_GENCTRL_SRC(GCLK_GENCTRL_SRC_OSC8M_Val)
                                | GCLK_GENCTRL_OE;
    
    // SERCOM 0 peripheral ; clock generator 0
    GCLK->CLKCTRL.reg           |= GCLK_CLKCTRL_ID(GCLK_CLKCTRL_ID_SERCOM0_CORE_Val)
                                | GCLK_CLKCTRL_GEN(GCLK_CLKCTRL_GEN_GCLK0_Val)
                                | GCLK_CLKCTRL_CLKEN;
    
    // Synchronous bus clock without prescaler
    PM->APBCSEL.reg             |= PM_APBCSEL_APBCDIV(PM_APBCSEL_APBCDIV_DIV1_Val);
    
    // Enable SERCOM 0
    PM->APBCMASK.reg            |= PM_APBCMASK_SERCOM0;
}

void configure_spi_master(void){
    // Software reset
    SERCOM0->SPI.CTRLA.reg          |= SERCOM_SPI_CTRLA_SWRST;
    while(SERCOM0->SPI.CTRLA.bit.SWRST){};                  // Wait until reset
    
    // SPI master ; SPI frame format ; DIPO 0x0; DOPO 0x1
    SERCOM0->SPI.CTRLA.reg          |= SERCOM_SPI_CTRLA_MODE(SERCOM_SPI_CTRLA_MODE_SPI_MASTER_Val)
                                    | SERCOM_SPI_CTRLA_FORM(0)
                                    | SERCOM_SPI_CTRLA_DIPO(0)
                                    | SERCOM_SPI_CTRLA_DOPO(1);
    
    // Slave select low detect enable ; Master slave select enable
    SERCOM0->SPI.CTRLB.reg          |= SERCOM_SPI_CTRLB_SSDE
                                    | SERCOM_SPI_CTRLB_MSSEN;
    
     /*
     / Fix the baud rate at 1000000
     / SystemCoreClock / (2 * baudrate) - 1
     / SystemCoreClock = 8000000
     / baudrate = 1000000
    */
    SERCOM0->SPI.BAUD.bit.BAUD      = (SPI_LIGHTING_MAIN_CLK_FREQ) / (2 * (SPI_LIGHTING_BAUDRATE)) - 1;
    
    // Configure PIN DIPO
    PORT->Group[0].PINCFG[4].bit.PMUXEN = 0x1; // Enable peripheral multiplexing
    PORT->Group[0].PMUX[2].bit.PMUXE = SPI_LIGHTING_PERIPHERAL_MUX_EVEN;
    PORT->Group[0].PINCFG[4].bit.INEN = 0x1; // Enable input
    
    // Configure PIN SS
    PORT->Group[0].PINCFG[5].bit.PMUXEN = 0x1; // Enable peripheral multiplexing
    PORT->Group[0].PMUX[2].bit.PMUXO = SPI_LIGHTING_PERIPHERAL_MUX_ODD;
    
    // Configure PIN DOPO
    PORT->Group[0].PINCFG[6].bit.PMUXEN = 0x1; // Enable peripheral multiplexing
    PORT->Group[0].PMUX[3].bit.PMUXE = SPI_LIGHTING_PERIPHERAL_MUX_EVEN;
    
    // Configure PIN SCK
    PORT->Group[0].PINCFG[7].bit.PMUXEN = 0x1; // Enable peripheral multiplexing
    PORT->Group[0].PMUX[3].bit.PMUXO = SPI_LIGHTING_PERIPHERAL_MUX_ODD;
    
    // Enable SPI
    SERCOM0->SPI.CTRLA.reg          |= SERCOM_SPI_CTRLA_ENABLE;
    while(!(SERCOM0->SPI.CTRLA.reg & SERCOM_SPI_CTRLA_ENABLE)){};               // Wait until SPI is enabled
        
    NVIC_SetPriority(SERCOM0_IRQn, 3);                                  // Set the interrupt priority to 3 (lowest value)
    NVIC_EnableIRQ(SERCOM0_IRQn);                                       // Enable the interrupt
}

The result I have is a random lighting of the LEDs. I cant be really more precise about what I obtain.

I think I'm missing something about slave configuration but I don't manage to find how to do it.

any idea ?

2

There are 2 best solutions below

0
On BEST ANSWER

Well... I found a solution that works a bit by chance... If you know the reason why do not hesitate to let me know.

By deleting the Slave Select pin configuration in the PMUX register's configuration, everything works perfectly (no idea why...).

My SPI bus configuration function is now :

#define SPI_LIGHTING_MAIN_CLK_FREQ    0x7A1200 // 8Mhz
#define SPI_LIGHTING_BAUDRATE         0xF4240  // 1000000

// Peripheral function D selected
#define USART_MIDI_PERIPHERAL_MUX_ODD     0x3
#define USART_MIDI_PERIPHERAL_MUX_EVEN    0x3

void configure_spi(void){
    // Software reset
    SERCOM0->SPI.CTRLA.reg            |= SERCOM_SPI_CTRLA_SWRST;
    while(SERCOM0->SPI.CTRLA.bit.SWRST){};                    // Wait until reset

    // SPI master ; SPI frame format ; DIPO 0x0; DOPO 0x1
    SERCOM0->SPI.CTRLA.reg            |= SERCOM_SPI_CTRLA_MODE(SERCOM_SPI_CTRLA_MODE_SPI_MASTER_Val)
                                      | SERCOM_SPI_CTRLA_FORM(0)
                                      | SERCOM_SPI_CTRLA_DIPO(0)
                                      | SERCOM_SPI_CTRLA_DOPO(1);

    // Slave select low detect enable ; Master slave selection enable
    SERCOM0->SPI.CTRLB.reg            |= SERCOM_SPI_CTRLB_MSSEN
                                      | SERCOM_SPI_CTRLB_SSDE;

     /*
     / Fix the baud rate at 1000000
     / SystemCoreClock / (2 * baudrate) - 1
     / SystemCoreClock = 8000000
     / baudrate = 1000000
    */
    SERCOM0->SPI.BAUD.bit.BAUD        = (float)(SPI_LIGHTING_MAIN_CLK_FREQ ) / (2 * (float)(SPI_LIGHTING_BAUDRATE )) - 1;

    // Configure PIN DIPO
    PORT->Group[0].PINCFG[4].bit.PMUXEN = 0x1;  // Enable peripheral multiplexing
    PORT->Group[0].PMUX[2].bit.PMUXE = SPI_LIGHTING_PERIPHERAL_MUX_EVEN;
    PORT->Group[0].PINCFG[4].bit.INEN = 0x1;    // Enable input

    // Configure PIN DOPO
    PORT->Group[0].PINCFG[6].bit.PMUXEN = 0x1; // Enable peripheral multiplexing
    PORT->Group[0].PMUX[3].bit.PMUXE = SPI_LIGHTING_PERIPHERAL_MUX_EVEN;

    // Configure PIN SCK
    PORT->Group[0].PINCFG[7].bit.PMUXEN = 0x1; // Enable peripheral multiplexing
    PORT->Group[0].PMUX[3].bit.PMUXO = SPI_LIGHTING_PERIPHERAL_MUX_ODD;

    // Enable SPI
    SERCOM0->SPI.CTRLA.reg            |= SERCOM_SPI_CTRLA_ENABLE;
    while(!(SERCOM0->SPI.CTRLA.reg & SERCOM_SPI_CTRLA_ENABLE)){};        // Wait until SPI is enabled

    NVIC_SetPriority(SERCOM0_IRQn, 3);                                   // Set the interrupt priority to 3 (lowest value)
    NVIC_EnableIRQ(SERCOM0_IRQn);                                        // Enable the interrupt
}
1
On

Nice that you got it working now :) As for why... I now have a qualified guess regarding why the original code didn't work.

I noticed there's a silicon errata (my browser says this link is unsafe due to lack of https) which could cause spurious /SS interrupts.

1.15.3 SPI with Slave Select Low Detection

If the SERCOM is enabled in SPI mode with SSL detection enabled (CTRLB.SSDE) and CTRLB.RXEN = 1, an erroneous slave select low interrupt (INTFLAG.SSL) can be generated.

Workaround
Enable the SERCOM first with CTRLB.RXEN = 0. In a subsequent write, set CTRLB.RXEN = 1.

Affected Silicon Revisions: A, B, C, D, E.

Your initial code used RXEN = 1 until I asked why in comments, so the above errata would perhaps apply until you removed that part.

I'm not certain that this is the cause of your problem, but it is something to keep in mind. Check which silicon revision you have - when buying evaluation boards, one usually gets old silicon since such boards are released very early on.