EEVblog Electronics Community Forum
Electronics => Microcontrollers => Topic started by: Yipee on April 15, 2021, 01:35:36 pm
-
Hi, I'm here again with the same problem. I can't receive three bytes sent via SPI. The communication runs at 12.5MHz, transaction appears about every 500 microseconds. Each transaction contains three bytes with 8 microseconds between bytes. The receiver uC is Microchip SAM C21 on Xplained board (exactly SERCOM5 on EXT2 header).
My best implementation was using DMAC and SPI drivers from ASF. It was working, but then I was developing other parts of application and now when I tested whole thing together I found that it does not work. Actually it received first few transactions okay and then next transactions was with 3rd byte always set to 0.
I assume that the uC is able to receive the communication without problems when it could receive few transactions correctly. I started to develop own SPI driver, but I need to keep the ASF due to the rest of application uses it (and there is no time to change it). So I make an empty ASF project where I made implementation based on example: https://microchipdeveloper.com/32arm:samc21-code-gcc-sercom-spi-slave-example (https://microchipdeveloper.com/32arm:samc21-code-gcc-sercom-spi-slave-example). According to LED it could receive something. The strange thing was that the LED changed state when 2nd or 3rd byte was received but it ignored first byte but it could be caused by clock configuration because I kept the default. I wanted to know what it received so I added CAN driver from ASF, but I also edited clock configuration. Since that time I can't receive anything from SPI even when I load previous application :-BROKE
Wire connection is still the same. Now I also tried to load app with SERCOM5 connected to SERCOM1 where SERCOM5 is master. It works better because it triggers receive interrupts, but received data are empty.
Is there a nice example how to periodically receive data via SPI (and could you share it with me please)? Or is it possible that the chip (shift register) is broken?
-
Do you actually program SPI to receive all 3 bytes?
12 MHz is not that fast. There is no reason it should miss them.
-
Yes I think.
The DMA implementation has the same configuration as SPI DMA example on asf.microchip.com site. I made size of buffer_rx three bytes big which should be enough since the size of copied memory is computed from it. Or did I miss anything?
When I did own driver, I created three uint8_t variables for data and one uint8_t for counter. When slave select switches to low, it should clear the counter to zero and with every received byte it should increment. I don't know how it could skip first byte. But it was just a prototype.
Unfortunately today I worked on different project and I don't have access to sources from home so I can provide parts of code on Monday if required.
-
At least provide clock configuration and SPI configuration details. And also the code that starts the DMA transfer.
If the project does not contain anything proprietary, share the whole project.
-
I'm sorry for the delay...
Unfortunately the project contains proprietary things. So I will share everything related to SPI:
I uploaded the required files to Google drive. Files mentioned below are in SPI_orig_proj.zip and in SPI_HW.zip there is a test project where I tried to create own driver before implementing it to main project. Link:https://drive.google.com/drive/folders/1HZlJX4PGAzB0Tq4RgGGzRb-SKkYCUjyk?usp=sharing (https://drive.google.com/drive/folders/1HZlJX4PGAzB0Tq4RgGGzRb-SKkYCUjyk?usp=sharing)
spi_slave.h:
#ifndef SPI_SLAVE_H_
#define SPI_SLAVE_H_
#define SPI_BUF_LENGTH 3
/************************************************************************/
/* DMA */
/************************************************************************/
#define CONF_PERIPHERAL_TRIGGER_RX SERCOM5_DMAC_ID_RX
/************************************************************************/
/* SPI */
/************************************************************************/
#define CONF_SLAVE_SPI_MODULE SERCOM5
#define CONF_SLAVE_MUX_SETTING SPI_SIGNAL_MUX_SETTING_E
#define CONF_SLAVE_PINMUX_PAD0 PINMUX_PB02D_SERCOM5_PAD0
#define CONF_SLAVE_PINMUX_PAD1 PINMUX_PB03D_SERCOM5_PAD1
#define CONF_SLAVE_PINMUX_PAD2 PINMUX_PB00D_SERCOM5_PAD2
#define CONF_SLAVE_PINMUX_PAD3 PINMUX_PB01D_SERCOM5_PAD3
void spi_slave_init(void);
bool spi_read_data(uint8_t *buffer_rx);
#endif /* SPI_SLAVE_H_ */
spi_slave.c:
#include <asf.h>
#include "spi_slave.h"
struct spi_module spi_slave_instance;
struct dma_resource example_resource_rx;
DmacDescriptor example_descriptor_rx SECTION_DMAC_DESCRIPTOR;
static volatile bool transfer_complete_spi_slave = false;
static volatile bool transfer_rx_is_done = false;
COMPILER_ALIGNED(32)
static uint8_t dma_buffer_rx[SPI_BUF_LENGTH];
/************************************************************************/
/* DMA */
/************************************************************************/
static void transfer_rx_done(struct dma_resource* const resource )
{
transfer_rx_is_done = true;
}
static void configure_dma_resource_rx(struct dma_resource *rx_resource)
{
struct dma_resource_config rx_config;
dma_get_config_defaults(&rx_config);
rx_config.peripheral_trigger = CONF_PERIPHERAL_TRIGGER_RX;
rx_config.trigger_action = DMA_TRIGGER_ACTION_BEAT;
dma_allocate(rx_resource, &rx_config);
}
static void setup_transfer_descriptor_rx(DmacDescriptor *rx_descriptor)
{
struct dma_descriptor_config rx_descriptor_config;
dma_descriptor_get_config_defaults(&rx_descriptor_config);
rx_descriptor_config.beat_size = DMA_BEAT_SIZE_BYTE;
rx_descriptor_config.src_increment_enable = false;
rx_descriptor_config.block_transfer_count = sizeof(dma_buffer_rx)/sizeof(uint8_t);
rx_descriptor_config.source_address = (uint32_t)(&spi_slave_instance.hw->SPI.DATA.reg);
rx_descriptor_config.destination_address = (uint32_t)dma_buffer_rx + sizeof(dma_buffer_rx);
dma_descriptor_create(rx_descriptor, &rx_descriptor_config);
}
/************************************************************************/
/* SPI */
/************************************************************************/
static void configure_spi_slave(void)
{
struct spi_config config_spi_slave;
/* Configure, initialize and enable SERCOM SPI module */
spi_get_config_defaults(&config_spi_slave);
config_spi_slave.mode = SPI_MODE_SLAVE;
config_spi_slave.mode_specific.slave.preload_enable = true;
config_spi_slave.mode_specific.slave.frame_format = SPI_FRAME_FORMAT_SPI_FRAME;
config_spi_slave.mux_setting = CONF_SLAVE_MUX_SETTING;
config_spi_slave.pinmux_pad0 = CONF_SLAVE_PINMUX_PAD0;
config_spi_slave.pinmux_pad1 = CONF_SLAVE_PINMUX_PAD1;
config_spi_slave.pinmux_pad2 = CONF_SLAVE_PINMUX_PAD2;
config_spi_slave.pinmux_pad3 = CONF_SLAVE_PINMUX_PAD3;
spi_init(&spi_slave_instance, CONF_SLAVE_SPI_MODULE, &config_spi_slave);
spi_enable(&spi_slave_instance);
}
void spi_slave_init(void)
{
configure_spi_slave();
configure_dma_resource_rx(&example_resource_rx);
setup_transfer_descriptor_rx(&example_descriptor_rx);
dma_add_descriptor(&example_resource_rx, &example_descriptor_rx);
dma_register_callback(&example_resource_rx, transfer_rx_done, DMA_CALLBACK_TRANSFER_DONE);
dma_enable_callback(&example_resource_rx, DMA_CALLBACK_TRANSFER_DONE);
dma_start_transfer_job(&example_resource_rx);
}
bool spi_read_data(uint8_t *buffer_rx) {
if (transfer_rx_is_done) {
transfer_rx_is_done = false;
memcpy(buffer_rx, dma_buffer_rx, SPI_BUF_LENGTH * sizeof(uint8_t)); // copy data out
//memset(dma_buffer_rx, 0, SPI_BUF_LENGTH * sizeof(uint8_t)); // clear buffer - not necessary but to be sure
dma_start_transfer_job(&example_resource_rx); // start reading SPI bus again
return true;
}
return false;
}
conf_clocks.h
/**
* \file
*
* \brief SAM C21 Clock configuration
*
* Copyright (c) 2014-2018 Microchip Technology Inc. and its subsidiaries.
*
* \asf_license_start
*
* \page License
*
* Subject to your compliance with these terms, you may use Microchip
* software and any derivatives exclusively with Microchip products.
* It is your responsibility to comply with third party license terms applicable
* to your use of third party software (including open source software) that
* may accompany Microchip software.
*
* THIS SOFTWARE IS SUPPLIED BY MICROCHIP "AS IS". NO WARRANTIES,
* WHETHER EXPRESS, IMPLIED OR STATUTORY, APPLY TO THIS SOFTWARE,
* INCLUDING ANY IMPLIED WARRANTIES OF NON-INFRINGEMENT, MERCHANTABILITY,
* AND FITNESS FOR A PARTICULAR PURPOSE. IN NO EVENT WILL MICROCHIP BE
* LIABLE FOR ANY INDIRECT, SPECIAL, PUNITIVE, INCIDENTAL OR CONSEQUENTIAL
* LOSS, DAMAGE, COST OR EXPENSE OF ANY KIND WHATSOEVER RELATED TO THE
* SOFTWARE, HOWEVER CAUSED, EVEN IF MICROCHIP HAS BEEN ADVISED OF THE
* POSSIBILITY OR THE DAMAGES ARE FORESEEABLE. TO THE FULLEST EXTENT
* ALLOWED BY LAW, MICROCHIP'S TOTAL LIABILITY ON ALL CLAIMS IN ANY WAY
* RELATED TO THIS SOFTWARE WILL NOT EXCEED THE AMOUNT OF FEES, IF ANY,
* THAT YOU HAVE PAID DIRECTLY TO MICROCHIP FOR THIS SOFTWARE.
*
* \asf_license_stop
*
*/
#include <clock.h>
#ifndef CONF_CLOCKS_H_INCLUDED
#define CONF_CLOCKS_H_INCLUDED
/* System clock bus configuration */
#define CONF_CLOCK_FLASH_WAIT_STATES 3
#define CONF_CLOCK_CPU_DIVIDER SYSTEM_MAIN_CLOCK_DIV_1
/* SYSTEM_CLOCK_SOURCE_OSC48M configuration - Internal 48MHz oscillator */
#define CONF_CLOCK_OSC48M_FREQ_DIV SYSTEM_OSC48M_DIV_1
#define CONF_CLOCK_OSC48M_ON_DEMAND true
#define CONF_CLOCK_OSC48M_RUN_IN_STANDBY false
/* SYSTEM_CLOCK_SOURCE_XOSC configuration - External clock/oscillator */
#define CONF_CLOCK_XOSC_ENABLE false
#define CONF_CLOCK_XOSC_EXTERNAL_CRYSTAL SYSTEM_CLOCK_EXTERNAL_CRYSTAL
#define CONF_CLOCK_XOSC_EXTERNAL_FREQUENCY 12000000UL
#define CONF_CLOCK_XOSC_STARTUP_TIME SYSTEM_XOSC_STARTUP_32768
#define CONF_CLOCK_XOSC_AUTO_GAIN_CONTROL true
#define CONF_CLOCK_XOSC_ON_DEMAND true
#define CONF_CLOCK_XOSC_RUN_IN_STANDBY false
#define CONF_CLOCK_XOSC_FAILURE_DETECTOR_PRE SYSTEM_CLOCK_XOSC_FAILURE_DETECTOR_PRESCALER_1
#define CONF_CLOCK_XOSC_FAILURE_DETECTOR_EVENT_OUTPUT_ENABLE false
#define CONF_CLOCK_XOSC_FAILURE_DETECTOR_ENABLE false
#define CONF_CLOCK_XOSC_FAILURE_SWITCH_BACK_ENABLE false
/* SYSTEM_CLOCK_SOURCE_XOSC32K configuration - External 32KHz crystal/clock oscillator */
#define CONF_CLOCK_XOSC32K_ENABLE true
#define CONF_CLOCK_XOSC32K_EXTERNAL_CRYSTAL SYSTEM_CLOCK_EXTERNAL_CRYSTAL
#define CONF_CLOCK_XOSC32K_STARTUP_TIME SYSTEM_XOSC32K_STARTUP_65536
#define CONF_CLOCK_XOSC32K_ENABLE_1KHZ_OUPUT false
#define CONF_CLOCK_XOSC32K_ENABLE_32KHZ_OUTPUT true
#define CONF_CLOCK_XOSC32K_ON_DEMAND false
#define CONF_CLOCK_XOSC32K_RUN_IN_STANDBY true
#define CONF_CLOCK_XOSC32K_FAILURE_DETECTOR_PRE SYSTEM_CLOCK_XOSC32K_FAILURE_DETECTOR_PRESCALER_1
#define CONF_CLOCK_XOSC32K_FAILURE_DETECTOR_EVENT_OUTPUT_ENABLE false
#define CONF_CLOCK_XOSC32K_FAILURE_DETECTOR_ENABLE false
#define CONF_CLOCK_XOSC32K_FAILURE_SWITCH_BACK_ENABLE false
/* SYSTEM_CLOCK_SOURCE_OSC32K configuration - Internal 32KHz oscillator */
#define CONF_CLOCK_OSC32K_ENABLE false
#define CONF_CLOCK_OSC32K_STARTUP_TIME SYSTEM_OSC32K_STARTUP_130
#define CONF_CLOCK_OSC32K_ENABLE_1KHZ_OUTPUT true
#define CONF_CLOCK_OSC32K_ENABLE_32KHZ_OUTPUT true
#define CONF_CLOCK_OSC32K_ON_DEMAND true
#define CONF_CLOCK_OSC32K_RUN_IN_STANDBY false
/* SYSTEM_CLOCK_SOURCE_DPLL configuration - Digital Phase-Locked Loop */
#define CONF_CLOCK_DPLL_ENABLE true
#define CONF_CLOCK_DPLL_ON_DEMAND false
#define CONF_CLOCK_DPLL_RUN_IN_STANDBY true
#define CONF_CLOCK_DPLL_LOCK_BYPASS false
#define CONF_CLOCK_DPLL_WAKE_UP_FAST false
#define CONF_CLOCK_DPLL_LOW_POWER_ENABLE false
#define CONF_CLOCK_DPLL_LOCK_TIME SYSTEM_CLOCK_SOURCE_DPLL_LOCK_TIME_DEFAULT
#define CONF_CLOCK_DPLL_REFERENCE_CLOCK SYSTEM_CLOCK_SOURCE_DPLL_REFERENCE_CLOCK_XOSC32K
#define CONF_CLOCK_DPLL_FILTER SYSTEM_CLOCK_SOURCE_DPLL_FILTER_DEFAULT
#define CONF_CLOCK_DPLL_PRESCALER SYSTEM_CLOCK_SOURCE_DPLL_DIV_1
#define CONF_CLOCK_DPLL_REFERENCE_FREQUENCY 32767
#define CONF_CLOCK_DPLL_REFERENCE_DIVIDER 1
#define CONF_CLOCK_DPLL_OUTPUT_FREQUENCY 48000000
/* DPLL GCLK reference configuration */
#define CONF_CLOCK_DPLL_REFERENCE_GCLK_GENERATOR GCLK_GENERATOR_1
/* DPLL GCLK 32K reference configuration */
#define CONF_CLOCK_DPLL_REFERENCE_GCLK_32K_GENERATOR GCLK_GENERATOR_1
/* Set this to true to configure the GCLK when running clocks_init. If set to
* false, none of the GCLK generators will be configured in clocks_init(). */
#define CONF_CLOCK_CONFIGURE_GCLK true
/* Configure GCLK generator 0 (Main Clock) */
#define CONF_CLOCK_GCLK_0_ENABLE true
#define CONF_CLOCK_GCLK_0_RUN_IN_STANDBY true
#define CONF_CLOCK_GCLK_0_CLOCK_SOURCE SYSTEM_CLOCK_SOURCE_OSC48M
#define CONF_CLOCK_GCLK_0_PRESCALER 1
#define CONF_CLOCK_GCLK_0_OUTPUT_ENABLE false
/* Configure GCLK generator 1 */
#define CONF_CLOCK_GCLK_1_ENABLE true
#define CONF_CLOCK_GCLK_1_RUN_IN_STANDBY true
#define CONF_CLOCK_GCLK_1_CLOCK_SOURCE SYSTEM_CLOCK_SOURCE_XOSC32K
#define CONF_CLOCK_GCLK_1_PRESCALER 1
#define CONF_CLOCK_GCLK_1_OUTPUT_ENABLE false
/* Configure GCLK generator 2 */
#define CONF_CLOCK_GCLK_2_ENABLE false
#define CONF_CLOCK_GCLK_2_RUN_IN_STANDBY false
#define CONF_CLOCK_GCLK_2_CLOCK_SOURCE SYSTEM_CLOCK_SOURCE_OSC48M
#define CONF_CLOCK_GCLK_2_PRESCALER 1
#define CONF_CLOCK_GCLK_2_OUTPUT_ENABLE false
/* Configure GCLK generator 3 */
#define CONF_CLOCK_GCLK_3_ENABLE false
#define CONF_CLOCK_GCLK_3_RUN_IN_STANDBY false
#define CONF_CLOCK_GCLK_3_CLOCK_SOURCE SYSTEM_CLOCK_SOURCE_OSC48M
#define CONF_CLOCK_GCLK_3_PRESCALER 1
#define CONF_CLOCK_GCLK_3_OUTPUT_ENABLE false
/* Configure GCLK generator 4 */
#define CONF_CLOCK_GCLK_4_ENABLE false
#define CONF_CLOCK_GCLK_4_RUN_IN_STANDBY false
#define CONF_CLOCK_GCLK_4_CLOCK_SOURCE SYSTEM_CLOCK_SOURCE_OSC48M
#define CONF_CLOCK_GCLK_4_PRESCALER 1
#define CONF_CLOCK_GCLK_4_OUTPUT_ENABLE false
/* Configure GCLK generator 5 */
#define CONF_CLOCK_GCLK_5_ENABLE false
#define CONF_CLOCK_GCLK_5_RUN_IN_STANDBY false
#define CONF_CLOCK_GCLK_5_CLOCK_SOURCE SYSTEM_CLOCK_SOURCE_OSC48M
#define CONF_CLOCK_GCLK_5_PRESCALER 1
#define CONF_CLOCK_GCLK_5_OUTPUT_ENABLE false
/* Configure GCLK generator 6 */
#define CONF_CLOCK_GCLK_6_ENABLE false
#define CONF_CLOCK_GCLK_6_RUN_IN_STANDBY false
#define CONF_CLOCK_GCLK_6_CLOCK_SOURCE SYSTEM_CLOCK_SOURCE_OSC48M
#define CONF_CLOCK_GCLK_6_PRESCALER 1
#define CONF_CLOCK_GCLK_6_OUTPUT_ENABLE false
/* Configure GCLK generator 7 */
#define CONF_CLOCK_GCLK_7_ENABLE false
#define CONF_CLOCK_GCLK_7_RUN_IN_STANDBY false
#define CONF_CLOCK_GCLK_7_CLOCK_SOURCE SYSTEM_CLOCK_SOURCE_OSC48M
#define CONF_CLOCK_GCLK_7_PRESCALER 1
#define CONF_CLOCK_GCLK_7_OUTPUT_ENABLE false
/* Configure GCLK generator 8 */
#define CONF_CLOCK_GCLK_8_ENABLE true
#define CONF_CLOCK_GCLK_8_RUN_IN_STANDBY false
#define CONF_CLOCK_GCLK_8_CLOCK_SOURCE SYSTEM_CLOCK_SOURCE_DPLL
#define CONF_CLOCK_GCLK_8_PRESCALER 1
#define CONF_CLOCK_GCLK_8_OUTPUT_ENABLE false
#endif /* CONF_CLOCKS_H_INCLUDED */
main:
-
To debug this, set a pin high before the call to dma_start_transfer_job() and set it low in transfer_rx_done(). Now capture SPI data and this pin at the same time using oscilloscope or a logic analyzer.
You may be missing some bytes because you don't start the DMA job in time or something like this.
I see nothing wrong with the code, and it should be able to receive bytes at 12 MHz SPI clock.
-
Thank you Alex.
I don't know how it looks like on oscilloscope because colleague took probes with himself and he is few days away but it seems you found the problem.
When I called the dma_start_transfer_job() in transfer_rx_done() then it received all 3 bytes correctly. Also it seems stable but when it boots in the middle of transaction then it shifts the bytes but I hope I will be able to fix it by catching the first SPI_INTERRUPT_FLAG_TX_COMPLETE.