1

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 ?

Fanch
  • 44
  • 7
  • When removing ASF you should still keep the named bit masks from that library, alternatively implement them yourself. `REGISTER = 0x1234; // comment explaining magic number` is poor style - you should replace the magic number with self-documenting named constants instead. – Lundin May 16 '22 at 12:40
  • Similarly, `((float)(0x7A1200) / (2 * (float)(0xF4240))) - 1;` is highly fishy code. You shouldn't use floating point anywhere in your code. And we have no idea where these magic numbers are coming from. Replace this with a named, pre-calculated integer constant. Explain in a comment how it was calculated. – Lundin May 16 '22 at 12:42
  • Why do you enable reception? Do you read back the value from the shift register or are there multiple shift registers in a "daisy chain"? Also, where are the ISRs? – Lundin May 16 '22 at 12:47
  • @Lundin : Thank you for your answers. I edited my code to use the named bit masks from "samd21" library instead of my bit masks. I also added some modifications in my code to reset SPI registers first. As you said, enabling reception is useless, i changed it. But I still don't manage to obtain the good result. – Fanch May 17 '22 at 07:44
  • You didn't fix the baudrate calculation though. Floating point needs to go, this is a Cortex M. It is not a PC, it does not have a FPU. Measure the baudrate you currently get with your scope, then measure it again after fixing the floating point equation. – Lundin May 17 '22 at 07:48
  • Sorry, no scope at home... actually the calculation of the baud rate is something I don't know very well... I tried to remove the "float" cast but it doesn't change anything. I also noticed that I didn't enabled "Master Slave Select Enable" bit which is, I think, necessary. – Fanch May 17 '22 at 08:03
  • SPI bus is a synchronous bus so the documentation says that BAUD register calculation is : "BAUD = (Fref / (2*Fbaud)) - 1" – Fanch May 17 '22 at 08:12
  • There is no sensible way to develop SPI code without access to an oscilloscope. You'll be fumbling around in the dark. As for your calculation it would seem it simply takes the clock given to the hardware peripheral and divides it by two? Then compensates with -1 for the register value. Like for example 8MHz clock, you want 100kbps baudrate: `(8MHz / (2*100kHz)) - 1` = `40 - 1`. Not rocket science, you shouldn't need to calculate this in run-time unless you wish to implement some means to dynamically change baudrate in run-time. Floating point only serves to screw up this calculation. – Lundin May 17 '22 at 08:41
  • I am aware of it indeed but I am constrained by the lack of space... Concerning the calculation of the BAUD register, it seems a little more complex than that from the understanding I have. What is called "Fbaud" in the documentation is actually the baud rate (bps). So depending on the baud rate I want to obtain, the frequency of the device clock will be divided by two times the value of the baud rate and then 1 is subtracted from the whole. This value is not intended to change during use and can be written in hard copy afterwards. – Fanch May 17 '22 at 09:02
  • Apart from making the code needlessly slow, the main problem with float is that in case the equation isn't evenly divisible, you may get different results when using float compared to fixed point. – Lundin May 17 '22 at 09:29
  • I modified my code by removing the "float" and also following a remark of somebody else who indicated me an error in the parameter setting of the pins of my multiplexer (I parametrized the register PMUX of all the odd pins on the bit PMUXE instead of PMUXO). The result obtained after modification is much better because I see that the message I want to transmit to turn on / off the LEDs is correctly interpreted. However a problem remains because all the LEDs remain permanently slightly lit. – Fanch May 17 '22 at 09:42

2 Answers2

1

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
}
Fanch
  • 44
  • 7
0

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.

Lundin
  • 195,001
  • 40
  • 254
  • 396
  • Indeed, it is quite possible that this is the reason why my previous code did not work (apart from my inattention errors in the setting of the mux pins...). Now the CTRLB.RXEN and CTRLB.SSDE bits are low so it should not matter but I have to keep in mind for the future. – Fanch May 18 '22 at 13:05