This repository has been archived on 2025-02-01. You can view files and clone it, but cannot push or open issues or pull requests.
reprapfirmware-dc42/Libraries/SPI/spi_master.c
David Crocker f5c4bdc770 Version 1.09r
Added timeout to output buffers destined for USB
Fixed bugs in thermocouple code
Reallocated thermocouple pin numbers
Made Roland mill and inkjet support conditional and normally disabled
Fixed issue with thermocouple code messing up the timekeeping system,
which was suspected of causing the network to be unreliable
Added support for M577 (thanks chrishamm)
2016-01-16 23:10:36 +00:00

615 lines
17 KiB
C

// ASF 3.27.0
/**
* \file
*
* \brief SPI master common service for SAM.
*
* Copyright (c) 2011-2015 Atmel Corporation. All rights reserved.
*
* \asf_license_start
*
* \page License
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* 1. Redistributions of source code must retain the above copyright notice,
* this list of conditions and the following disclaimer.
*
* 2. Redistributions in binary form must reproduce the above copyright notice,
* this list of conditions and the following disclaimer in the documentation
* and/or other materials provided with the distribution.
*
* 3. The name of Atmel may not be used to endorse or promote products derived
* from this software without specific prior written permission.
*
* 4. This software may only be redistributed and used in connection with an
* Atmel microcontroller product.
*
* THIS SOFTWARE IS PROVIDED BY ATMEL "AS IS" AND ANY EXPRESS OR IMPLIED
* WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT ARE
* EXPRESSLY AND SPECIFICALLY DISCLAIMED. IN NO EVENT SHALL ATMEL BE LIABLE FOR
* ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
* OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
* HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
* STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
* ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
* POSSIBILITY OF SUCH DAMAGE.
*
* \asf_license_stop
*
*/
/**
* Support and FAQ: visit <a href="http://www.atmel.com/design-support/">Atmel Support</a>
*/
#include "Arduino.h"
#include "compiler.h"
#include "spi_master.h"
#include "variant.h"
#ifndef F_CPU
#define F_CPU 84000000UL
#endif
#ifndef MOSI_PIN
#define MOSI_PIN MOSI
#endif
#ifndef MISO_PIN
#define MISO_PIN MISO
#endif
#ifndef SCK_PIN
#define SCK_PIN SCK
#endif
#define WRITE(pin, v) do{if(v) {g_APinDescription[pin].pPort->PIO_SODR = g_APinDescription[pin].ulPin;} else {g_APinDescription[pin].pPort->PIO_CODR = g_APinDescription[pin].ulPin; }}while(0)
/**
* \brief Max number when the chip selects are connected to a 4- to 16-bit decoder.
*/
#define MAX_NUM_WITH_DECODER 0x10
/**
* \brief Max number when the chip selects are directly connected to peripheral device.
*/
#define MAX_NUM_WITHOUT_DECODER 0x04
/**
* \brief Max number when the chip selects are directly connected to peripheral device.
*/
#define NONE_CHIP_SELECT_ID 0x0f
/**
* \brief The default chip select id.
*/
#define DEFAULT_CHIP_ID 1
/**
* \brief Issue a LASTXFER command.
* The next transfer is the last transfer and after that CS is de-asserted.
*
* \param p_spi Pointer to an SPI instance.
*/
static inline void spi_set_lastxfer(Spi *p_spi)
{
p_spi->SPI_CR = SPI_CR_LASTXFER;
}
/**
* \brief Configure CS behavior for SPI transfer (\ref spi_cs_behavior_t).
*
* \param p_spi Pointer to an SPI instance.
* \param ul_pcs_ch Peripheral Chip Select channel (0~3).
* \param ul_cs_behavior Behavior of the Chip Select after transfer.
*/
void spi_configure_cs_behavior(Spi *p_spi, uint32_t ul_pcs_ch,
uint32_t ul_cs_behavior)
{
if (ul_cs_behavior == SPI_CS_RISE_FORCED) {
p_spi->SPI_CSR[ul_pcs_ch] &= (~SPI_CSR_CSAAT);
p_spi->SPI_CSR[ul_pcs_ch] |= SPI_CSR_CSNAAT;
} else if (ul_cs_behavior == SPI_CS_RISE_NO_TX) {
p_spi->SPI_CSR[ul_pcs_ch] &= (~SPI_CSR_CSAAT);
p_spi->SPI_CSR[ul_pcs_ch] &= (~SPI_CSR_CSNAAT);
} else if (ul_cs_behavior == SPI_CS_KEEP_LOW) {
p_spi->SPI_CSR[ul_pcs_ch] |= SPI_CSR_CSAAT;
}
}
/**
* \brief Set number of bits per transfer.
*
* \param p_spi Pointer to an SPI instance.
* \param ul_pcs_ch Peripheral Chip Select channel (0~3).
* \param ul_bits Number of bits (8~16), use the pattern defined
* in the device header file.
*/
void spi_set_bits_per_transfer(Spi *p_spi, uint32_t ul_pcs_ch,
uint32_t ul_bits)
{
p_spi->SPI_CSR[ul_pcs_ch] &= (~SPI_CSR_BITS_Msk);
p_spi->SPI_CSR[ul_pcs_ch] |= ul_bits;
}
/**
* \brief Configure timing for SPI transfer.
*
* \param p_spi Pointer to an SPI instance.
* \param ul_pcs_ch Peripheral Chip Select channel (0~3).
* \param uc_dlybs Delay before SPCK (in number of MCK clocks).
* \param uc_dlybct Delay between consecutive transfers (in number of MCK clocks
).
*/
void spi_set_transfer_delay(Spi *p_spi, uint32_t ul_pcs_ch,
uint8_t uc_dlybs, uint8_t uc_dlybct)
{
p_spi->SPI_CSR[ul_pcs_ch] &= ~(SPI_CSR_DLYBS_Msk | SPI_CSR_DLYBCT_Msk);
p_spi->SPI_CSR[ul_pcs_ch] |= SPI_CSR_DLYBS(uc_dlybs)
| SPI_CSR_DLYBCT(uc_dlybct);
}
/**
* \brief Set clock default state.
*
* \param p_spi Pointer to an SPI instance.
* \param ul_pcs_ch Peripheral Chip Select channel (0~3).
* \param ul_polarity Default clock state is logical one(high)/zero(low).
*/
void spi_set_clock_polarity(Spi *p_spi, uint32_t ul_pcs_ch,
uint32_t ul_polarity)
{
if (ul_polarity) {
p_spi->SPI_CSR[ul_pcs_ch] |= SPI_CSR_CPOL;
} else {
p_spi->SPI_CSR[ul_pcs_ch] &= (~SPI_CSR_CPOL);
}
}
/**
* \brief Set Data Capture Phase.
*
* \param p_spi Pointer to an SPI instance.
* \param ul_pcs_ch Peripheral Chip Select channel (0~3).
* \param ul_phase Data capture on the rising/falling edge of clock.
*/
void spi_set_clock_phase(Spi *p_spi, uint32_t ul_pcs_ch, uint32_t ul_phase)
{
if (ul_phase) {
p_spi->SPI_CSR[ul_pcs_ch] |= SPI_CSR_NCPHA;
} else {
p_spi->SPI_CSR[ul_pcs_ch] &= (~SPI_CSR_NCPHA);
}
}
/** \brief Initialize the SPI in master mode.
*
* \param p_spi Base address of the SPI instance.
*
*/
void spi_master_init(Spi *p_spi, int ul_cs_pin, int ul_npcs_pin)
{
static bool init_comms = true;
if (init_comms)
{
PIO_Configure(
g_APinDescription[SCK_PIN].pPort,
g_APinDescription[SCK_PIN].ulPinType,
g_APinDescription[SCK_PIN].ulPin,
g_APinDescription[SCK_PIN].ulPinConfiguration);
PIO_Configure(
g_APinDescription[MOSI_PIN].pPort,
g_APinDescription[MOSI_PIN].ulPinType,
g_APinDescription[MOSI_PIN].ulPin,
g_APinDescription[MOSI_PIN].ulPinConfiguration);
PIO_Configure(
g_APinDescription[MISO_PIN].pPort,
g_APinDescription[MISO_PIN].ulPinType,
g_APinDescription[MISO_PIN].ulPin,
g_APinDescription[MISO_PIN].ulPinConfiguration);
pmc_enable_periph_clk(ID_SPI0);
spi_reset(p_spi);
// set master mode, peripheral select, disable fault detection
SPI_Configure(p_spi, ID_SPI0,
SPI_MR_MSTR | // Master mode
SPI_MR_MODFDIS); // Disable mode fault detection
SPI_Enable(p_spi);
init_comms = false;
}
if (ul_npcs_pin >= 0)
{
// Connect the NPCS pin as a PIO peripheral and assert it HIGH
PIO_Configure(
g_APinDescription[ul_npcs_pin].pPort,
g_APinDescription[ul_npcs_pin].ulPinType,
g_APinDescription[ul_npcs_pin].ulPin,
g_APinDescription[ul_npcs_pin].ulPinConfiguration);
WRITE(ul_npcs_pin, HIGH);
}
if (ul_cs_pin >= 0)
{
PIO_Configure(
g_APinDescription[ul_cs_pin].pPort,
PIO_OUTPUT_1,
g_APinDescription[ul_cs_pin].ulPin,
g_APinDescription[ul_cs_pin].ulPinConfiguration);
WRITE(ul_cs_pin, HIGH);
}
#if defined(USE_SAM3X_DMAC)
pmc_enable_periph_clk(ID_DMAC);
dmac_disable(DMAC);
dmac_set_priority_mode(DMAC, DMAC_GCFG_ARB_CFG_FIXED);
dmac_enable(DMAC);
#endif
// spi_enable_clock(p_spi);
// spi_reset(p_spi);
// spi_set_master_mode(p_spi);
// spi_disable_mode_fault_detect(p_spi);
// spi_disable_loopback(p_spi);
// spi_set_peripheral_chip_select_value(p_spi, DEFAULT_CHIP_ID);
// spi_set_fixed_peripheral_select(p_spi);
// spi_disable_peripheral_select_decode(p_spi);
// spi_set_delay_between_chip_select(p_spi, CONFIG_SPI_MASTER_DELAY_BCS);
// SPI_Enable(p_spi);
}
/**
* \brief Calculate the baudrate divider.
*
* \param baudrate Baudrate value.
* \param mck SPI module input clock frequency (MCK clock, Hz).
*
* \return Divider or error code.
* \retval > 0 Success.
* \retval < 0 Error.
*/
int16_t spi_calc_baudrate_div(uint32_t baud_rate, uint32_t mck)
{
int16_t baud_div = div_ceil(mck, baud_rate);
/* The value of baud_div is from 1 to 255 in the SCBR field. */
if (baud_div <= 0)
baud_div = 10;
else if (baud_div > 255)
baud_div = 255;
return baud_div;
}
/**
* \brief Set up an SPI device.
*
* The returned device descriptor structure must be passed to the driver
* whenever that device should be used as current slave device.
*
* \param p_spi Base address of the SPI instance.
* \param device Pointer to SPI device struct that should be initialized.
* \param flags SPI configuration flags. Common flags for all
* implementations are the SPI modes SPI_MODE_0 ...
* SPI_MODE_3.
* \param baud_rate Baud rate for communication with slave device in Hz.
* \param sel_id Board specific select id.
*/
void spi_master_setup_device(Spi *p_spi, const struct spi_device *device,
spi_flags_t flags, uint32_t baud_rate, board_spi_select_id_t sel_id)
{
/* avoid Cppcheck Warning */
UNUSED(sel_id);
UNUSED(flags);
spi_reset(p_spi);
SPI_Configure(p_spi, ID_SPI0,
SPI_MR_MSTR | // Master mode
SPI_MR_MODFDIS); // Disable mode fault detection
// Set SPI mode 0, clock, select not active after transfer
// with delay between transfers
int16_t baud_div = spi_calc_baudrate_div(baud_rate, F_CPU);
SPI_ConfigureNPCS(p_spi, device->id,
SPI_CSR_NCPHA | // Data capture on rising edge of clock
SPI_CSR_CSAAT | // CS behavior == SPI_CS_KEEP_LOW
SPI_CSR_SCBR(baud_div) | // Baud rate
device->bits | // Transfer bit width
SPI_CSR_DLYBCT(1)); // Transfer delay
SPI_Enable(p_spi);
// spi_set_bits_per_transfer(p_spi, device->id, device->bits);
// spi_set_transfer_delay(p_spi, device->id, CONFIG_SPI_MASTER_DELAY_BS,
// CONFIG_SPI_MASTER_DELAY_BCT);
// spi_set_baudrate_div(p_spi, device->id,
// spi_calc_baudrate_div(baud_rate, F_CPU));
// spi_configure_cs_behavior(p_spi, device->id, SPI_CS_KEEP_LOW);
// spi_set_clock_polarity(p_spi, device->id, flags >> 1);
// spi_set_clock_phase(p_spi, device->id, ((flags & 0x1) ^ 0x1));
}
/**
* \brief Select the given device on the SPI bus.
*
* Set device specific setting and call board chip select.
*
* \param p_spi Base address of the SPI instance.
* \param device SPI device.
*
*/
void spi_select_device(Spi *p_spi, const struct spi_device *device)
{
if (spi_get_peripheral_select_decode_setting(p_spi)) {
if (device->id < MAX_NUM_WITH_DECODER) {
spi_set_peripheral_chip_select_value(p_spi, device->id);
}
} else {
if (device->id < MAX_NUM_WITHOUT_DECODER) {
spi_set_peripheral_chip_select_value(p_spi, (~(1 << device->id)));
}
}
// Enable the CS line
WRITE(device->cs, LOW);
}
/**
* \brief Deselect the given device on the SPI bus.
*
* Call board chip deselect.
*
* \param p_spi Base address of the SPI instance.
* \param device SPI device.
*
* \pre SPI device must be selected with spi_select_device() first.
*/
void spi_deselect_device(Spi *p_spi, const struct spi_device *device)
{
uint32_t timeout = SPI_TIMEOUT;
while (!spi_is_tx_empty(p_spi)) {
if (!timeout--) {
return;
}
}
// Last transfer, so de-assert the current NPCS if CSAAT is set.
spi_set_lastxfer(p_spi);
// Disable the CS line
WRITE(device->cs, HIGH);
// Assert all lines; no peripheral is selected.
spi_set_peripheral_chip_select_value(p_spi, NONE_CHIP_SELECT_ID);
}
/**
* \brief Send a sequence of bytes to an SPI device.
*
* Received bytes on the SPI bus are discarded.
*
* \param p_spi Base address of the SPI instance.
* \param data Data buffer to write.
* \param len Length of data to be written.
*
* \pre SPI device must be selected with spi_select_device() first.
*/
status_code_t spi_write_packet(Spi *p_spi, const uint8_t *data, size_t len)
{
if (len == 0)
return STATUS_OK;
for (size_t i = 0; i < len-1; i++)
{
uint32_t timeout = SPI_TIMEOUT;
while (!spi_is_tx_ready(p_spi)) {
if (!timeout--) {
return ERR_TMO;
}
}
p_spi->SPI_TDR = (uint32_t)data[i];
while (!spi_is_rx_ready(p_spi)) {
if (!timeout--) {
return ERR_TMO;
}
}
p_spi->SPI_RDR;
}
return spi_write_single(p_spi, data[len-1]);
}
/**
* \brief Receive a sequence of bytes from an SPI device.
*
* All bytes sent out on SPI bus are sent as value 0.
*
* \param p_spi Base address of the SPI instance.
* \param data Data buffer to read.
* \param len Length of data to be read.
*
* \pre SPI device must be selected with spi_select_device() first.
*/
status_code_t spi_read_packet(Spi *p_spi, uint8_t *data, size_t len)
{
if (len-- == 0)
return STATUS_OK;
for (uint16_t i = 0; i < len; i++)
{
uint32_t timeout = SPI_TIMEOUT;
while (!spi_is_tx_ready(p_spi)) {
if (!timeout--) {
return ERR_TMO;
}
}
p_spi->SPI_TDR = 0x000000FF;
timeout = SPI_TIMEOUT;
while (!spi_is_rx_ready(p_spi)) {
if (!timeout--) {
return ERR_TMO;
}
}
data[i] = p_spi->SPI_RDR;
}
return spi_read_single(p_spi, &data[len]);
}
/**
* \brief Set Peripheral Chip Select (PCS) value.
*
* \param p_spi Pointer to an SPI instance.
* \param ul_value Peripheral Chip Select value.
* If PCS decode mode is not used, use \ref spi_get_pcs to build
* the value to use.
* On reset the decode mode is not enabled.
* The decode mode can be enabled/disabled by follow functions:
* \ref spi_enable_peripheral_select_decode,
* \ref spi_disable_peripheral_select_decode.
*/
void spi_set_peripheral_chip_select_value(Spi *p_spi, uint32_t ul_value)
{
p_spi->SPI_MR &= (~SPI_MR_PCS_Msk);
p_spi->SPI_MR |= SPI_MR_PCS(ul_value);
}
/**
* \brief Set delay between chip selects (in number of MCK clocks).
* If DLYBCS <= 6, 6 MCK clocks will be inserted by default.
*
* \param p_spi Pointer to an SPI instance.
* \param ul_delay Delay between chip selects (in number of MCK clocks).
*/
void spi_set_delay_between_chip_select(Spi *p_spi, uint32_t ul_delay)
{
p_spi->SPI_MR &= (~SPI_MR_DLYBCS_Msk);
p_spi->SPI_MR |= SPI_MR_DLYBCS(ul_delay);
}
/**
* \brief Set Serial Clock Baud Rate divider value (SCBR).
*
* \param p_spi Pointer to an SPI instance.
* \param ul_pcs_ch Peripheral Chip Select channel (0~3).
* \param uc_baudrate_divider Baudrate divider from MCK.
*/
void spi_set_baudrate_div(Spi *p_spi, uint32_t ul_pcs_ch,
uint8_t uc_baudrate_divider)
{
p_spi->SPI_CSR[ul_pcs_ch] &= (~SPI_CSR_SCBR_Msk);
p_spi->SPI_CSR[ul_pcs_ch] |= SPI_CSR_SCBR(uc_baudrate_divider);
}
#if defined(USE_SAM3X_DMAC)
void spi_start_transmit_dma(Dmac *p_dmac, Spi *p_spi, uint32_t ul_num,
const void *src, uint32_t nb_bytes)
{
static uint8_t ff = 0xFF;
uint32_t cfg, src_incr = DMAC_CTRLB_SRC_INCR_INCREMENTING;
dma_transfer_descriptor_t desc;
// Send 0xFF repeatedly if src is NULL
if (!src) {
src = &ff;
src_incr = DMAC_CTRLB_SRC_INCR_FIXED;
}
// Disable the DMA channel prior to configuring
dmac_enable(p_dmac);
dmac_channel_disable(p_dmac, ul_num);
cfg = DMAC_CFG_SOD
| DMAC_CFG_DST_H2SEL
| DMAC_CFG_DST_PER(SPI_TX_IDX)
| DMAC_CFG_FIFOCFG_ALAP_CFG;
dmac_channel_set_configuration(p_dmac, ul_num, cfg);
// Prepare DMA transfer
desc.ul_source_addr = (uint32_t)src;
desc.ul_destination_addr = (uint32_t)&(p_spi->SPI_TDR);
desc.ul_ctrlA = DMAC_CTRLA_BTSIZE(nb_bytes)
| DMAC_CTRLA_SRC_WIDTH_BYTE
| DMAC_CTRLA_DST_WIDTH_BYTE;
desc.ul_ctrlB = DMAC_CTRLB_SRC_DSCR
| DMAC_CTRLB_DST_DSCR
| DMAC_CTRLB_FC_MEM2PER_DMA_FC
| src_incr
| DMAC_CTRLB_DST_INCR_FIXED;
// Next field is ignored, but set it anyway
desc.ul_descriptor_addr = (uint32_t)NULL;
// Finish configuring the transfer
dmac_channel_single_buf_transfer_init(p_dmac, ul_num, &desc);
// And now start the DMA transfer
dmac_channel_enable(p_dmac, ul_num);
}
void spi_start_receive_dma(Dmac *p_dmac, Spi *p_spi, uint32_t ul_num,
const void *dest, uint32_t nb_bytes)
{
uint32_t cfg;
dma_transfer_descriptor_t desc;
// clear any potential overrun error
cfg = p_spi->SPI_SR;
// Turn the DMA channel off before configuring
dmac_enable(p_dmac);
dmac_channel_disable(p_dmac, ul_num);
cfg = DMAC_CFG_SOD
| DMAC_CFG_SRC_H2SEL
| DMAC_CFG_SRC_PER(SPI_RX_IDX)
| DMAC_CFG_FIFOCFG_ASAP_CFG;
dmac_channel_set_configuration(p_dmac, ul_num, cfg);
// Prepare DMA transfer
desc.ul_source_addr = (uint32_t)&(p_spi->SPI_RDR);
desc.ul_destination_addr = (uint32_t)dest;
desc.ul_ctrlA = DMAC_CTRLA_BTSIZE(nb_bytes)
| DMAC_CTRLA_SRC_WIDTH_BYTE
| DMAC_CTRLA_DST_WIDTH_BYTE;
desc.ul_ctrlB = DMAC_CTRLB_SRC_DSCR
| DMAC_CTRLB_DST_DSCR
| DMAC_CTRLB_FC_PER2MEM_DMA_FC
| DMAC_CTRLB_SRC_INCR_FIXED
| DMAC_CTRLB_DST_INCR_INCREMENTING;
// This next field is ignored but set it anyway
desc.ul_descriptor_addr = (uint32_t)NULL;
// Finish configuring the DMA transfer
dmac_channel_single_buf_transfer_init(p_dmac, ul_num, &desc);
// And now allow the DMA transfer to begin
dmac_channel_enable(p_dmac, ul_num);
}
#endif
//! @}