You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
287 lines
7.7 KiB
287 lines
7.7 KiB
/* |
|
|
|
(c) 2017 night_ghost@ykoctpa.ru |
|
|
|
based on: datasheet and logical analyser |
|
|
|
*/ |
|
|
|
|
|
#pragma GCC optimize ("O2") |
|
|
|
#include <spi.h> |
|
#include <hal.h> |
|
|
|
/* |
|
* SPI devices |
|
*/ |
|
|
|
static spi_state spi1_state IN_CCM; |
|
static spi_state spi2_state IN_CCM; |
|
static spi_state spi3_state IN_CCM; |
|
|
|
static const spi_dev spi1 = { |
|
.SPIx = SPI1, |
|
.afio = GPIO_AF_SPI1, |
|
.irq = SPI1_IRQn, |
|
.clock = RCC_APB2Periph_SPI1, |
|
.dma = { DMA_CR_CH3, DMA2_STREAM2, DMA2_STREAM3 }, // SPI1, see Errata: DMA2 transfers can spoils data |
|
.state = &spi1_state, |
|
}; |
|
/** SPI device 1 */ |
|
const spi_dev * const _SPI1 = &spi1; |
|
|
|
static const spi_dev spi2 = { |
|
.SPIx = SPI2, |
|
.afio = GPIO_AF_SPI2, |
|
.irq = SPI2_IRQn, |
|
.clock = RCC_APB1Periph_SPI2, |
|
.dma = { DMA_CR_CH0, DMA1_STREAM3, DMA1_STREAM4 }, // SPI2 |
|
.state = &spi2_state, |
|
}; |
|
/** SPI device 2 */ |
|
const spi_dev * const _SPI2 = &spi2; |
|
|
|
static const spi_dev spi3 = { |
|
.SPIx = SPI3, |
|
.afio = GPIO_AF_SPI3, |
|
.irq = SPI3_IRQn, |
|
.clock = RCC_APB1Periph_SPI3, |
|
.dma = { DMA_CR_CH0, DMA1_STREAM2, DMA1_STREAM5 }, // SPI3 |
|
.state = &spi3_state, |
|
}; |
|
/** SPI device 3 */ |
|
const spi_dev * const _SPI3 = &spi3; |
|
|
|
|
|
void spi_init(const spi_dev *dev) { |
|
|
|
if (dev->SPIx == SPI1) { |
|
RCC_APB2PeriphResetCmd(RCC_APB2Periph_SPI1, ENABLE); |
|
RCC_APB2PeriphResetCmd(RCC_APB2Periph_SPI1, DISABLE); |
|
} else if (dev->SPIx == SPI2) { |
|
RCC_APB1PeriphResetCmd(RCC_APB1Periph_SPI2, ENABLE); |
|
RCC_APB1PeriphResetCmd(RCC_APB1Periph_SPI2, DISABLE); |
|
} else if (dev->SPIx == SPI3) { |
|
RCC_APB1PeriphResetCmd(RCC_APB1Periph_SPI3, ENABLE); |
|
RCC_APB1PeriphResetCmd(RCC_APB1Periph_SPI3, DISABLE); |
|
} else if (dev->SPIx == SPI4) { |
|
RCC_APB2PeriphResetCmd(RCC_APB2Periph_SPI4, ENABLE); |
|
RCC_APB2PeriphResetCmd(RCC_APB2Periph_SPI4, DISABLE); |
|
} else if (dev->SPIx == SPI5) { |
|
RCC_APB2PeriphResetCmd(RCC_APB2Periph_SPI5, ENABLE); |
|
RCC_APB2PeriphResetCmd(RCC_APB2Periph_SPI5, DISABLE); |
|
} else if (dev->SPIx == SPI6) { |
|
RCC_APB2PeriphResetCmd(RCC_APB2Periph_SPI6, ENABLE); |
|
RCC_APB2PeriphResetCmd(RCC_APB2Periph_SPI6, DISABLE); |
|
} |
|
} |
|
|
|
/** |
|
* @brief Call a function on each SPI port |
|
* @param fn Function to call. |
|
*/ |
|
void spi_foreach(void (*fn)(const spi_dev*)) { |
|
fn(_SPI1); |
|
fn(_SPI2); |
|
fn(_SPI3); |
|
} |
|
|
|
|
|
|
|
void spi_gpio_master_cfg(const spi_dev *dev, |
|
const gpio_dev *comm_dev, |
|
uint8_t sck_bit, |
|
uint8_t miso_bit, |
|
uint8_t mosi_bit) { |
|
|
|
|
|
/* Configure SCK pin */ |
|
gpio_set_mode(comm_dev, sck_bit, GPIO_AF_OUTPUT_PP); |
|
gpio_set_af_mode(comm_dev, sck_bit, dev->afio); |
|
gpio_set_speed(comm_dev, sck_bit, GPIO_speed_100MHz); |
|
|
|
/* Configure MISO pin */ |
|
gpio_set_mode(comm_dev, miso_bit, GPIO_AF_OUTPUT_OD_PU); |
|
gpio_set_af_mode(comm_dev, miso_bit, dev->afio); |
|
gpio_set_speed(comm_dev, miso_bit, GPIO_speed_100MHz); |
|
|
|
/* Configure MOSI pin */ |
|
gpio_set_mode(comm_dev, mosi_bit, GPIO_AF_OUTPUT_PP); |
|
gpio_set_af_mode(comm_dev, mosi_bit, dev->afio); |
|
gpio_set_speed(comm_dev, mosi_bit, GPIO_speed_100MHz); |
|
} |
|
|
|
void spi_gpio_slave_cfg(const spi_dev *dev, |
|
const gpio_dev *comm_dev, |
|
uint8_t sck_bit, |
|
uint8_t miso_bit, |
|
uint8_t mosi_bit) { |
|
|
|
/* Configure SCK pin */ |
|
gpio_set_mode(comm_dev, sck_bit, GPIO_INPUT_FLOATING); |
|
/* Configure MISO pin */ |
|
gpio_set_mode(comm_dev, miso_bit, GPIO_AF_OUTPUT_PP); |
|
/* Configure MOSI pin */ |
|
gpio_set_mode(comm_dev, mosi_bit, GPIO_INPUT_FLOATING); |
|
} |
|
|
|
/* |
|
* SPI auxiliary routines |
|
*/ |
|
void spi_reconfigure(const spi_dev *dev, uint8_t ismaster, uint16_t baudPrescaler, uint16_t bitorder, uint8_t mode) { |
|
SPI_InitTypeDef SPI_InitStructure; |
|
|
|
memset(dev->state, 0, sizeof(spi_state)); |
|
|
|
spi_disable_irq(dev, SPI_INTERRUPTS_ALL); |
|
|
|
spi_init(dev); |
|
|
|
spi_peripheral_disable(dev); |
|
|
|
/* Enable the SPI clock */ |
|
if (dev->SPIx == SPI1) |
|
RCC_APB2PeriphClockCmd(dev->clock, ENABLE); |
|
else if (dev->SPIx == SPI2) |
|
RCC_APB1PeriphClockCmd(RCC_APB1Periph_SPI2, ENABLE); |
|
else |
|
RCC_APB1PeriphClockCmd(RCC_APB1Periph_SPI3, ENABLE); |
|
|
|
/* SPI configuration */ |
|
SPI_StructInit(&SPI_InitStructure); |
|
SPI_InitStructure.SPI_Direction = SPI_Direction_2Lines_FullDuplex; |
|
SPI_InitStructure.SPI_DataSize = SPI_DataSize_8b; |
|
|
|
switch(mode) { |
|
case SPI_MODE_0: |
|
SPI_InitStructure.SPI_CPOL = SPI_CPOL_Low; |
|
SPI_InitStructure.SPI_CPHA = SPI_CPHA_1Edge; |
|
break; |
|
case SPI_MODE_1: |
|
SPI_InitStructure.SPI_CPOL = SPI_CPOL_Low; |
|
SPI_InitStructure.SPI_CPHA = SPI_CPHA_2Edge; |
|
break; |
|
case SPI_MODE_2: |
|
SPI_InitStructure.SPI_CPOL = SPI_CPOL_High; |
|
SPI_InitStructure.SPI_CPHA = SPI_CPHA_1Edge; |
|
break; |
|
case SPI_MODE_3: |
|
SPI_InitStructure.SPI_CPOL = SPI_CPOL_High; |
|
SPI_InitStructure.SPI_CPHA = SPI_CPHA_2Edge; |
|
break; |
|
default: |
|
break; |
|
} |
|
|
|
|
|
SPI_InitStructure.SPI_NSS = SPI_NSS_Soft; |
|
SPI_InitStructure.SPI_BaudRatePrescaler = baudPrescaler; |
|
if (bitorder == LSBFIRST) |
|
SPI_InitStructure.SPI_FirstBit = SPI_FirstBit_LSB; |
|
else |
|
SPI_InitStructure.SPI_FirstBit = SPI_FirstBit_MSB; |
|
|
|
SPI_InitStructure.SPI_CRCPolynomial = 7; |
|
if (ismaster) |
|
SPI_InitStructure.SPI_Mode = SPI_Mode_Master; |
|
else |
|
SPI_InitStructure.SPI_Mode = SPI_Mode_Slave; |
|
|
|
SPI_Init(dev->SPIx, &SPI_InitStructure); |
|
|
|
SPI_Cmd(dev->SPIx, ENABLE); |
|
|
|
uint32_t dly=1000; |
|
while (SPI_I2S_GetFlagStatus(dev->SPIx, SPI_BIT_TXE) == RESET) { |
|
dly--; |
|
if(dly==0) break; |
|
} |
|
|
|
(void) dev->SPIx->DR; |
|
} |
|
|
|
|
|
void spi_set_speed(const spi_dev *dev, uint16_t baudPrescaler) { |
|
|
|
#define BR_CLEAR_MASK 0xFFC7 |
|
|
|
SPI_Cmd(dev->SPIx, DISABLE); |
|
|
|
dev->SPIx->CR1 = (dev->SPIx->CR1 & BR_CLEAR_MASK) | baudPrescaler; |
|
|
|
SPI_Cmd(dev->SPIx, ENABLE); |
|
} |
|
|
|
|
|
|
|
// Transmit command and/or receive result in bidirectional master mode |
|
int spimaster_transfer(const spi_dev *dev, |
|
const uint8_t *txbuf, |
|
uint16_t txcount, |
|
uint8_t *rxbuf, |
|
uint16_t rxcount) |
|
{ |
|
uint16_t txc_in=txcount; |
|
|
|
// Transfer command data out |
|
|
|
while (txcount--){ |
|
while (!(dev->SPIx->SR & SPI_BIT_TXE)){ // just for case |
|
if(!spi_is_busy(dev) ) break; |
|
} |
|
dev->SPIx->DR = *txbuf++; |
|
uint16_t dly=1000; // ~20uS so byte already transferred |
|
while (!(dev->SPIx->SR & SPI_BIT_RXNE)) { |
|
if(--dly==0) break; |
|
} |
|
(void) dev->SPIx->DR; // read out unneeded data |
|
} |
|
|
|
if(txc_in && rxcount) delay_ns100(5); // small delay between TX and RX, to give the chip time to think over domestic affairs |
|
|
|
// Transfer response data in |
|
while (rxcount--){ |
|
while (!(dev->SPIx->SR & SPI_BIT_TXE)); |
|
dev->SPIx->DR = 0xFF; |
|
uint16_t dly=1000; |
|
while (!(dev->SPIx->SR & SPI_BIT_RXNE)) { |
|
if(--dly==0) break; |
|
} |
|
*rxbuf++ = dev->SPIx->DR; |
|
} |
|
|
|
// Wait until the transfer is complete - to not disable CS too early |
|
uint32_t dly=3000; |
|
while (dev->SPIx->SR & SPI_BIT_BSY){ // but datasheet prohibits this usage |
|
dly--; |
|
if(dly==0) break; |
|
} |
|
|
|
return 0; |
|
} |
|
|
|
|
|
static void isr_handler(const spi_dev *dev){ |
|
NVIC_ClearPendingIRQ(dev->irq); |
|
if(dev->state->handler) revo_call_handler(dev->state->handler, dev->SPIx->SR); |
|
else { // disable interrupts |
|
spi_disable_irq(dev, SPI_INTERRUPTS_ALL); |
|
} |
|
} |
|
|
|
void SPI1_IRQHandler(); |
|
void SPI2_IRQHandler(); |
|
void SPI3_IRQHandler(); |
|
|
|
void SPI1_IRQHandler() { |
|
isr_handler(&spi1); |
|
} |
|
|
|
void SPI2_IRQHandler() { |
|
isr_handler(&spi2); |
|
} |
|
|
|
void SPI3_IRQHandler() { |
|
isr_handler(&spi3); |
|
}
|
|
|