/// -*- tab-width: 4; Mode: C++; c-basic-offset: 4; indent-tabs-mode: nil -*- /* (c) 2017 night_ghost@ykoctpa.ru */ #pragma GCC optimize ("O2") #include #include #include "Semaphores.h" #include #include #include #include #include "Scheduler.h" #include #include #include "SPIDevice.h" #include "GPIO.h" using namespace F4Light; extern const SPIDesc spi_device_table[]; // different SPI tables per board subtype extern const uint8_t F4Light_SPI_DEVICE_NUM_DEVICES; static const spi_pins board_spi_pins[] = { { // 0 BOARD_SPI1_SCK_PIN, BOARD_SPI1_MISO_PIN, BOARD_SPI1_MOSI_PIN }, { // 1 BOARD_SPI2_SCK_PIN, BOARD_SPI2_MISO_PIN, BOARD_SPI2_MOSI_PIN }, { //2 BOARD_SPI3_SCK_PIN, BOARD_SPI3_MISO_PIN, BOARD_SPI3_MOSI_PIN } }; F4Light::Semaphore SPIDevice::_semaphores[MAX_BUS_NUM] IN_CCM; // per bus data void * SPIDevice::owner[MAX_BUS_NUM] IN_CCM; uint8_t * SPIDevice::buffer[MAX_BUS_NUM] IN_CCM; // DMA buffers in RAM #ifdef DEBUG_SPI spi_trans SPIDevice::spi_trans_array[SPI_LOG_SIZE] IN_CCM; uint8_t SPIDevice::spi_trans_ptr=0; #endif #ifdef BOARD_SOFTWARE_SPI // as fast as possible #define SCK_H {sck_port->BSRRL = sck_pin; } #define SCK_L {sck_port->BSRRH = sck_pin; } #define MOSI_H {mosi_port->BSRRL = mosi_pin; } #define MOSI_L {mosi_port->BSRRH = mosi_pin; } #define MISO_read ((miso_port->IDR & miso_pin)!=0) static void dly_spi() { delay_ns100(dly_time); }; uint8_t SPIDevice::_transfer_s(uint8_t bt) { for(int ii = 0; ii < 8; ++ii) { if (bt & 0x80) { MOSI_H; } else { MOSI_L; } SCK_L; dly_spi(); SCK_H; bt <<= 1; if (MISO_read) { bt |= 1; } dly_spi(); } return bt; } void SPIDevice::spi_soft_set_speed(){ uint16_t rate; switch(_speed) { case SPI_36MHZ: case SPI_18MHZ: case SPI_9MHZ: case SPI_4_5MHZ: rate = 1; break; case SPI_2_25MHZ: rate = 2; break; case SPI_1_125MHZ: rate = 4; // 400 ns delay break; case SPI_562_500KHZ: rate = 8; break; case SPI_281_250KHZ: rate = 16; break; case SPI_140_625KHZ: default: rate = 32; break; } dly_time = rate; } #endif void SPIDevice::register_completion_callback(Handler h) { if(_completion_cb){ // IOC from last call still not called - timeout // TODO: ??? } _completion_cb = h; } uint8_t SPIDevice::transfer(uint8_t out){ #ifdef BOARD_SOFTWARE_SPI if(_desc.mode == SPI_TRANSFER_SOFT) { return _transfer_s(out); } else #endif { return _transfer(out); } } uint8_t SPIDevice::_transfer(uint8_t data) { (void)_desc.dev->SPIx->DR; // read fake data out //wait for TXE before send while (!spi_is_tx_empty(_desc.dev)) { // should wait transfer finished if(!spi_is_busy(_desc.dev) ) break; } //write 1byte _desc.dev->SPIx->DR = data; //wait for read byte while (!spi_is_rx_nonempty(_desc.dev)) { // should wait transfer finished if(!spi_is_busy(_desc.dev) ) break; } return (uint8_t)(_desc.dev->SPIx->DR); // we got a byte so transfer complete } void SPIDevice::send(uint8_t out) { //wait for TXE before send while (!spi_is_tx_empty(_desc.dev)) { // should wait transfer finished if(!spi_is_busy(_desc.dev) ) break; } //write 1byte spi_tx_reg(_desc.dev, out); // _desc.dev->SPIx->DR = data; } bool SPIDevice::transfer(const uint8_t *out, uint32_t send_len, uint8_t *recv, uint32_t recv_len){ uint8_t err=1; // differrent devices on bus requires different modes if(owner[_desc.bus-1] != this) { // bus was in use by another driver so SPI hardware need reinit _initialized=false; } if(!_initialized){ init(); if(!_initialized) return false; owner[_desc.bus-1] = this; // Got it! } #ifdef BOARD_SOFTWARE_SPI // not deleted for case of needs for external SPI on arbitrary pins if(_desc.mode == SPI_TRANSFER_SOFT) { spi_soft_set_speed(); _cs_assert(); if (out != NULL && send_len) { for (uint16_t i = 0; i < send_len; i++) { _transfer_s(out[i]); } } if(recv !=NULL && recv_len) { for (uint16_t i = 0; i < recv_len; i++) { recv[i] = _transfer_s(0xff); } } _cs_release(); if(_completion_cb) { revo_call_handler(_completion_cb, (uint32_t)&_desc); _completion_cb=0; } } else #endif { uint32_t t = hal_micros(); while(_desc.dev->state->busy){ // wait for previous transfer finished if(hal_micros() - t > 5000){ // TODO increment grab counter break; // SPI transfer can't be so long so let grab the bus } hal_yield(0); } spi_set_speed(_desc.dev, determine_baud_rate(_speed)); _desc.dev->state->busy = true; // we got bus _cs_assert(); _send_address = out; // remember transfer params for ISR _send_len = send_len; _dummy_len = send_len; _recv_address = recv; _recv_len = recv_len; #define MIN_DMA_BYTES 1 // 4 // write to 2-byte register not uses DMA, 4-byte command to DataFlash will //#define MIN_DMA_BYTES 32 // for debug switch(_desc.mode){ case SPI_TRANSFER_DMA: // DMA if(send_len + recv_len >= MIN_DMA_BYTES) { get_dma_ready(); uint8_t nb = _desc.bus-1; bool can_dma = false; _desc.dev->state->len=0; // alloc buffer if recv needed - to not in ISR if(recv_len && !ADDRESS_IN_RAM(recv)){ //not in CCM if(buffer[nb] == NULL){ buffer[nb] = (uint8_t *)malloc(SPI_BUFFER_SIZE); // allocate only on 1st use } } _isr_mode = SPI_ISR_NONE; if(send_len){ //[ for debug // if(send_len>1){ //] if(ADDRESS_IN_RAM(out)){ // not in CCM can_dma=true; } else { if(send_len<=SPI_BUFFER_SIZE){ if(buffer[nb] == NULL){ // allocate only on 1st use buffer[nb] = (uint8_t *)malloc(SPI_BUFFER_SIZE); } if(buffer[nb]){ memmove(buffer[nb],out,send_len); out = buffer[nb]; _send_len=0; can_dma=true; } } } //[ for debug // } //] if(can_dma){ setup_dma_transfer(out, NULL, send_len); } else { _isr_mode = SPI_ISR_SEND_DMA; // try DMA on receive setup_isr_transfer(); } } else { // send_len == 0 so there will no RXNE interrupts so // we need setup DMA RX transfer here uint16_t len = _recv_len; if(ADDRESS_IN_RAM(recv)){ //not in CCM _desc.dev->state->len=0; can_dma=true; }else if(len<=SPI_BUFFER_SIZE && buffer[nb]){ _desc.dev->state->len=len; _desc.dev->state->dst=recv; recv = buffer[nb]; can_dma=true; } if(can_dma){ setup_dma_transfer(NULL, recv, len); _recv_len=0; } else { _isr_mode = SPI_ISR_RECEIVE; // just receive setup_isr_transfer(); } } err = do_transfer(can_dma, send_len + recv_len); break; } // no break! case SPI_TRANSFER_INTR: // interrupts _isr_mode = _send_len ? SPI_ISR_SEND : SPI_ISR_RECEIVE; setup_isr_transfer(); err=do_transfer(false, send_len + recv_len); break; case SPI_TRANSFER_POLL: // polling err = spimaster_transfer(_desc.dev, out, send_len, recv, recv_len); _cs_release(); _desc.dev->state->busy=false; if(_completion_cb) { revo_call_handler(_completion_cb, (uint32_t)&_desc); _completion_cb=0; } break; default: break; } } return err==0; } // not used anywhere bool SPIDevice::transfer_fullduplex(const uint8_t *out, uint8_t *recv, uint32_t len) { if(owner[_desc.bus-1] != this) { // bus was in use by another driver so need reinit _initialized=false; } if(!_initialized) { init(); if(!_initialized) return false; owner[_desc.bus-1] = this; // Got it! } #ifdef BOARD_SOFTWARE_SPI if(_desc.mode == SPI_TRANSFER_SOFT) { _cs_assert(); spi_soft_set_speed(); if (out != NULL && recv !=NULL && len) { for (uint16_t i = 0; i < len; i++) { recv[i] = _transfer_s(out[i]); } } return true; } else #endif { spi_set_speed(_desc.dev, determine_baud_rate(_speed)); _cs_assert(); switch(_desc.mode){ case SPI_TRANSFER_DMA: if((out==NULL || ADDRESS_IN_RAM(out)) && (recv==NULL || ADDRESS_IN_RAM(recv)) ) { setup_dma_transfer(out, recv, len); return do_transfer(true, len)==0; } // no break; case SPI_TRANSFER_INTR: // interrupts _isr_mode = SPI_ISR_RXTX; setup_isr_transfer(); return do_transfer(false, len)==0; case SPI_TRANSFER_POLL: // polling default: if (out != NULL && recv !=NULL && len) { for (uint16_t i = 0; i < len; i++) { recv[i] = _transfer(out[i]); } } _cs_release(); return true; } } return false; } AP_HAL::OwnPtr SPIDeviceManager::_get_device(const char *name) { const SPIDesc *desc = nullptr; /* Find the bus description in the table */ for (uint8_t i = 0; i < F4Light_SPI_DEVICE_NUM_DEVICES; i++) { if (!strcmp(spi_device_table[i].name, name)) { desc = &spi_device_table[i]; break; } } if (!desc) { AP_HAL::panic("SPI: invalid device name"); } return AP_HAL::OwnPtr(new SPIDevice(*desc)); } SPIDevice::SPIDevice(const SPIDesc &device_desc) : _desc(device_desc) , _initialized(false) , _completion_cb(0) { if(_desc.cs_pin < BOARD_NR_GPIO_PINS) { _cs = GPIO::get_channel(_desc.cs_pin); if (!_cs) { AP_HAL::panic("SPI: wrong CS pin"); } } else { _cs = NULL; // caller itself controls CS } } const spi_pins* SPIDevice::dev_to_spi_pins(const spi_dev *dev) { if ( dev->SPIx == SPI1) return &board_spi_pins[0]; else if (dev->SPIx == SPI2) return &board_spi_pins[1]; else if (dev->SPIx == SPI3) return &board_spi_pins[2]; else { assert_param(0); return NULL; } } spi_baud_rate SPIDevice::determine_baud_rate(SPIFrequency freq) { spi_baud_rate rate; switch(freq) { case SPI_36MHZ: rate = SPI_BAUD_PCLK_DIV_2; byte_time = 1; // time in 0.25uS units break; case SPI_18MHZ: rate = SPI_BAUD_PCLK_DIV_4; byte_time = 2; break; case SPI_9MHZ: rate = SPI_BAUD_PCLK_DIV_8; byte_time = 4; break; case SPI_4_5MHZ: rate = SPI_BAUD_PCLK_DIV_16; byte_time = 8; break; case SPI_2_25MHZ: rate = SPI_BAUD_PCLK_DIV_32; byte_time = 16; break; case SPI_1_125MHZ: rate = SPI_BAUD_PCLK_DIV_64; byte_time = 32; break; case SPI_562_500KHZ: rate = SPI_BAUD_PCLK_DIV_128; byte_time = 64; break; case SPI_281_250KHZ: rate = SPI_BAUD_PCLK_DIV_256; byte_time = 128; break; case SPI_140_625KHZ: rate = SPI_BAUD_PCLK_DIV_256; byte_time = 255; break; default: rate = SPI_BAUD_PCLK_DIV_32; byte_time = 16; break; } return rate; } void SPIDevice::get_dma_ready(){ const Spi_DMA &dp = _desc.dev->dma; // check for DMA not busy before use uint32_t t = hal_micros(); while(dma_is_stream_enabled(dp.stream_rx) || dma_is_stream_enabled(dp.stream_tx) ) { // wait for previous transfer termination if(hal_micros() - t > 1000) break; // DMA transfer can't be more than 1ms hal_yield(0); } spi_disable_irq(_desc.dev, SPI_RXNE_TXE_INTERRUPTS); // just for case } // DMA dummy workplace static uint32_t rw_workbyte[] = { 0xffff }; // not in stack! void SPIDevice::setup_dma_transfer(const uint8_t *out, const uint8_t *recv, uint32_t btr){ DMA_InitType DMA_InitStructure; const Spi_DMA &dp = _desc.dev->dma; uint32_t memory_inc; dma_init(dp.stream_rx); dma_init(dp.stream_tx); dma_clear_isr_bits(dp.stream_rx); dma_clear_isr_bits(dp.stream_tx); DMA_InitStructure.DMA_PeripheralBaseAddr = (uint32_t)(&(_desc.dev->SPIx->DR)); DMA_InitStructure.DMA_BufferSize = btr; DMA_InitStructure.DMA_FIFO_flags = DMA_FIFOThreshold_Full | DMA_FIFOMode_Disable; // TODO use FIFO on large transfers // receive stream if(recv) { DMA_InitStructure.DMA_Memory0BaseAddr = (uint32_t)recv; memory_inc = DMA_CR_MINC; } else { DMA_InitStructure.DMA_Memory0BaseAddr = (uint32_t)rw_workbyte; memory_inc = 0; } DMA_InitStructure.DMA_flags = DMA_CR_MSIZE_8BITS | DMA_CR_PSIZE_8BITS | DMA_CR_MBURST0 | DMA_CR_PBURST0 | DMA_CR_DIR_P2M | memory_inc | dp.channel | _desc.prio; dma_init_transfer(dp.stream_rx, &DMA_InitStructure); // transmit stream if(out) { DMA_InitStructure.DMA_Memory0BaseAddr = (uint32_t)out; memory_inc = DMA_CR_MINC; } else { DMA_InitStructure.DMA_Memory0BaseAddr = (uint32_t)rw_workbyte; memory_inc = 0; } DMA_InitStructure.DMA_flags = DMA_CR_MSIZE_8BITS | DMA_CR_PSIZE_8BITS | DMA_CR_MBURST0 | DMA_CR_PBURST0 | DMA_CR_DIR_M2P | memory_inc | dp.channel | _desc.prio; dma_init_transfer(dp.stream_tx, &DMA_InitStructure); dma_enable(dp.stream_rx); dma_enable(dp.stream_tx); // run them both! // we attach interrupt on RX channel's TransferComplete, so at ISR time bus will be free dma_attach_interrupt(dp.stream_rx, Scheduler::get_handler(FUNCTOR_BIND_MEMBER(&SPIDevice::dma_isr, void)), DMA_CR_TCIE); } void SPIDevice::start_dma_transfer(){ spi_enable_dma_req(_desc.dev, SPI_DMAreq_Rx | SPI_DMAreq_Tx); /* Enable SPI TX/RX request */ } void SPIDevice::disable_dma(){ const Spi_DMA &dp = _desc.dev->dma; dma_disable(dp.stream_rx); dma_disable(dp.stream_tx); dma_detach_interrupt(dp.stream_rx); // we attach interrupt each request // Disable SPI RX/TX request spi_disable_dma_req(_desc.dev, SPI_DMAreq_Rx | SPI_DMAreq_Tx); dma_clear_isr_bits(dp.stream_rx); dma_clear_isr_bits(dp.stream_tx); } void SPIDevice::dma_isr(){ disable_dma(); if(_desc.dev->state->len) { memmove(_desc.dev->state->dst, &buffer[_desc.bus-1][0], _desc.dev->state->len); _desc.dev->state->len=0; // once } _send_len = 0; // send done // we attach interrupt on RX channel's TransferComplete, so at ISR bus will be free if(_recv_len) { // now we should program DMA for receive or turn on ISR mode receiving if can't DMA (void)_desc.dev->SPIx->DR; // read fake data out // now we should set up DMA transfer spi_wait_busy(_desc.dev); // just for case - RX transfer is finished delay_ns100(3); // small delay between TX and RX, to give the chip time to think over domestic affairs // for slow devices which need a time between address and data // 200ns uses setup_dma_transfer() itself bool can_dma = false; uint8_t *recv = _recv_address; uint16_t len = _recv_len; uint8_t nb = _desc.bus-1; if(ADDRESS_IN_RAM(recv)){ //not in CCM _desc.dev->state->len=0; can_dma=true; }else if(len<=SPI_BUFFER_SIZE && buffer[nb]){ _desc.dev->state->len=len; _desc.dev->state->dst=recv; recv = buffer[nb]; can_dma=true; } if(can_dma){ _isr_mode = SPI_ISR_NONE; spi_disable_irq(_desc.dev, SPI_RXNE_INTERRUPT); // disable RXNE interrupt, TXE already disabled setup_dma_transfer(NULL, recv, len); _recv_len = 0; // receive by DMA start_dma_transfer(); } else { _isr_mode = SPI_ISR_RECEIVE; // just receive setup_isr_transfer(); spi_enable_irq(_desc.dev, SPI_TXE_INTERRUPT); // enable - will be interrupt just immediate } } else { // all done isr_transfer_finish(); // releases SPI bus } } void SPIDevice::init(){ if(_cs) { _cs->mode(OUTPUT); _cs_release(); // do not hold the SPI bus initially } _completion_cb=0; const spi_pins *pins = dev_to_spi_pins(_desc.dev); if (!pins || pins->sck > BOARD_NR_GPIO_PINS || pins->mosi > BOARD_NR_GPIO_PINS || pins->miso > BOARD_NR_GPIO_PINS) { return; } #ifdef BOARD_SOFTWARE_SPI if(_desc.mode == SPI_TRANSFER_SOFT) { //software { // isolate p const stm32_pin_info &p = PIN_MAP[pins->sck]; const gpio_dev *sck_dev = p.gpio_device; uint8_t sck_bit = p.gpio_bit; gpio_set_mode(sck_dev, sck_bit, GPIO_OUTPUT_PP); gpio_set_speed(sck_dev, sck_bit, GPIO_speed_100MHz); gpio_write_bit(sck_dev, sck_bit, 1); // passive SCK high sck_port = sck_dev->GPIOx; sck_pin = 1<mosi]; const gpio_dev *mosi_dev = p.gpio_device; uint8_t mosi_bit = p.gpio_bit; gpio_set_mode(mosi_dev, mosi_bit, GPIO_OUTPUT_PP); gpio_set_speed(mosi_dev, mosi_bit, GPIO_speed_100MHz); gpio_write_bit(mosi_dev, mosi_bit, 1); // passive MOSI high mosi_port = mosi_dev->GPIOx; mosi_pin = 1<miso]; const gpio_dev *miso_dev = p.gpio_device; uint8_t miso_bit = p.gpio_bit; gpio_set_mode(miso_dev, miso_bit, GPIO_INPUT_PU); gpio_set_speed(miso_dev, miso_bit, GPIO_speed_100MHz); miso_port = miso_dev->GPIOx; miso_pin = 1<miso]; spi_gpio_master_cfg(_desc.dev, miso.gpio_device, PIN_MAP[pins->sck].gpio_bit, miso.gpio_bit, PIN_MAP[pins->mosi].gpio_bit); spi_master_enable(_desc.dev, determine_baud_rate(_desc.lowspeed), _desc.sm, MSBFIRST); } _initialized=true; } bool SPIDevice::set_speed(AP_HAL::Device::Speed speed) { //* this requires for 1-byte transfers if(owner[_desc.bus-1] != this) { // bus was in use by another driver so need reinit _initialized=false; } if(!_initialized) { init(); if(!_initialized) return false; owner[_desc.bus-1] = this; // Got it! } //*/ switch (speed) { case AP_HAL::Device::SPEED_HIGH: _speed = _desc.highspeed; break; case AP_HAL::Device::SPEED_LOW: default: _speed = _desc.lowspeed; break; } return true; } // start transfer and wait until it finished uint8_t SPIDevice::do_transfer(bool is_DMA, uint32_t nbytes) { #ifdef DEBUG_SPI // spi_trans_ptr=0; // each transfer from start spi_trans &p = spi_trans_array[spi_trans_ptr]; p.time = hal_micros(); p.dev = _desc.dev; p.send_len = _send_len; p.recv_len = _recv_len; p.dummy_len=_dummy_len; p.data = *_send_address; p.cr2 = 0; p.sr1 = 0; p.mode = _isr_mode; p.act = 0xff; spi_trans_ptr++; if(spi_trans_ptr>=SPI_LOG_SIZE) spi_trans_ptr=0; #endif if(_completion_cb) {// we should call it after completion via interrupt if(is_DMA) { start_dma_transfer(); } else { spi_enable_irq(_desc.dev, SPI_RXNE_TXE_INTERRUPTS );// enable both interrupts - will be interrupt just immediate } return 0; // all another in ISR } // no callback - need to wait uint32_t timeout = nbytes * 16; // time to transfer all data - 16uS per byte uint32_t t=hal_micros(); _task = Scheduler::get_current_task(); EnterCriticalSection; // we are in multitask so if task switch occures between enable and pause then all transfer occures before task will be paused // so task will be paused after transfer and never be resumed - so will cause timeout if(_task) Scheduler::task_pause(timeout); if(is_DMA) { // Enable SPI TX/RX request start_dma_transfer(); } else { spi_enable_irq(_desc.dev, SPI_RXNE_TXE_INTERRUPTS); // enable both interrupts - will be interrupt just immediate } LeaveCriticalSection; while (hal_micros()-t < timeout && _desc.dev->state->busy) { hal_yield(0); // пока ждем пусть другие работают. } _task = NULL; // already resumed if(_desc.dev->state->busy) { // timeout, so there was no ISR, so #ifdef DEBUG_SPI p.sr1=0x80; #endif if(is_DMA) disable_dma(); isr_transfer_finish(); // disable interrupts } return (_send_len == 0 && _recv_len == 0)? 0 : 1; } void SPIDevice::setup_isr_transfer() { spi_attach_interrupt(_desc.dev, Scheduler::get_handler(FUNCTOR_BIND_MEMBER(&SPIDevice::spi_isr, void)) ); (void)_desc.dev->SPIx->DR; // read fake data out } uint16_t SPIDevice::send_strobe(const uint8_t *buffer, uint16_t len){ // send in ISR and strobe each byte by CS while(_desc.dev->state->busy) hal_yield(0); // wait for previous transfer finished _send_address = buffer; _send_len = len; _recv_len = 0; _isr_mode = SPI_ISR_STROBE; spi_attach_interrupt(_desc.dev, Scheduler::get_handler(FUNCTOR_BIND_MEMBER(&SPIDevice::spi_isr, void)) ); _cs->_write(0); _desc.dev->state->busy=true; (void)_desc.dev->SPIx->DR; // read fake data out //[ write out 1st byte to do RXNE interrupt _send_len--; // write 1st byte to start transfer uint8_t b = *_send_address++; _desc.dev->SPIx->DR = b; //] (void) do_transfer(false, len); return _send_len; } // gives received bytes to callback and returns when callback returns true but not linger than timeout (uS) // so it works like wait for needed byte in ISR - but without wait uint8_t SPIDevice::wait_for(uint8_t out, spi_WaitFunc cb, uint32_t dly){ // wait for needed byte in ISR _send_len = out; _isr_mode = SPI_ISR_COMPARE; _recv_len = 0; // we shouldn't receive after wait _compare_cb = cb; spi_attach_interrupt(_desc.dev, Scheduler::get_handler(FUNCTOR_BIND_MEMBER(&SPIDevice::spi_isr, void)) ); uint32_t t = hal_micros(); _desc.dev->state->busy=true; // need to wait until transfer complete _task = Scheduler::get_current_task(); (void)_desc.dev->SPIx->DR; // read fake data out _desc.dev->SPIx->DR = out; // start transfer and clear flag EnterCriticalSection; // prevent from task switch if(_task) Scheduler::task_pause(dly); // if function called from task - store it and pause spi_enable_irq(_desc.dev, SPI_RXNE_TXE_INTERRUPTS); // enable both - will be interrupt on next line LeaveCriticalSection; while (hal_micros() - t < dly && _desc.dev->state->busy) { hal_yield(0); } _task = NULL; // already resumed if(_desc.dev->state->busy) isr_transfer_finish(); // timeout return _recv_data; } // releases SPI bus after transfer finished, call callback and resume task void SPIDevice::isr_transfer_finish(){ spi_disable_irq(_desc.dev, SPI_RXNE_TXE_INTERRUPTS); // just for case spi_detach_interrupt(_desc.dev); while (spi_is_rx_nonempty(_desc.dev)) { // read out fake data for TX only transfers (void)spi_rx_reg(_desc.dev); } _desc.dev->state->busy=false; // reset if(_task){ // resume paused task Scheduler::task_resume(_task); // task will be resumed having very high priority & force // context switch just after return from ISR so task will get a tick _task=NULL; } spi_wait_busy(_desc.dev); // just for case - we already after last RXNE if(_isr_mode != SPI_ISR_COMPARE) { _cs_release(); // free bus } Handler h; if((h=_completion_cb)) { _completion_cb=0; // only once and BEFORE call itself because IOC can do new transfer revo_call_handler(h, (uint32_t)&_desc); } } void SPIDevice::spi_isr(){ #ifdef DEBUG_SPI spi_trans &p = spi_trans_array[spi_trans_ptr]; p.time = hal_micros(); p.dev = _desc.dev; p.send_len = _send_len; p.recv_len = _recv_len; p.dummy_len=_dummy_len; p.cr2 = _desc.dev->SPIx->CR2; p.sr1 = _desc.dev->SPIx->SR; p.mode = _isr_mode; p.act = 0; spi_trans_ptr++; if(spi_trans_ptr>=SPI_LOG_SIZE) spi_trans_ptr=0; #endif if(spi_is_tx_empty(_desc.dev) && spi_is_irq_enabled(_desc.dev, SPI_TXE_INTERRUPT)) { #ifdef DEBUG_SPI p.act |= 1; #endif switch(_isr_mode) { case SPI_ISR_NONE: (void)_desc.dev->SPIx->DR; // reset RXNE spi_disable_irq(_desc.dev, SPI_TXE_INTERRUPT); // disable TXE interrupt _isr_mode=SPI_ISR_FINISH; // should releases SPI bus after last RXNE is set break; case SPI_ISR_SEND: case SPI_ISR_SEND_DMA: if(_send_len) { _send_len--; _desc.dev->SPIx->DR = *_send_address++; } else { // all sent // now we in 1 byte till bus release, so wait for RXNE spi_disable_irq(_desc.dev, SPI_TXE_INTERRUPT); // disable TXE interrupt if(_recv_len) { if(_isr_mode==SPI_ISR_SEND) { _isr_mode=SPI_ISR_WAIT_RX; // switch receive mode after receiving of fake RX byte } else if(_isr_mode==SPI_ISR_SEND_DMA) { _isr_mode=SPI_ISR_WAIT_RX_DMA; // will switch to DMA receive mode after receiving of fake RX byte } else { _isr_mode=SPI_ISR_FINISH; // should release SPI bus after last RXNE is set } } else { _isr_mode=SPI_ISR_FINISH; // should release SPI bus after last RXNE is set } } break; case SPI_ISR_RXTX: _desc.dev->SPIx->DR = *_send_address++; break; case SPI_ISR_RECEIVE: if(_recv_len > 1) { // not on last byte _desc.dev->SPIx->DR = 0xFF; // dummy byte } else { spi_disable_irq(_desc.dev, SPI_TXE_INTERRUPT); // disable TXE interrupt } break; case SPI_ISR_COMPARE: case SPI_ISR_STROBE: default: spi_disable_irq(_desc.dev, SPI_TXE_INTERRUPT); // disable unneeded TXE interrupt break; } } if(spi_is_rx_nonempty(_desc.dev) /* && spi_is_irq_enabled(_desc.dev, SPI_RXNE_INTERRUPT) */) { #ifdef DEBUG_SPI p.act |= 2; #endif switch(_isr_mode) { case SPI_ISR_STROBE: { (void)_desc.dev->SPIx->DR; // read fake data out if(_send_len){ spi_wait_busy(_desc.dev); delay_ns100(1); _cs->_write(1); delay_ns100(1); _send_len--; uint8_t b = *_send_address++; _cs->_write(0); delay_ns100(1); _desc.dev->SPIx->DR = b; } else { isr_transfer_finish(); // releases SPI bus after transfer complete } } break; case SPI_ISR_SEND: case SPI_ISR_SEND_DMA: (void)_desc.dev->SPIx->DR; // read fake data out _dummy_len--; // and count them break; case SPI_ISR_RECEIVE: if(_recv_len){ _recv_len--; *_recv_address++ = _desc.dev->SPIx->DR; } if(_recv_len==0) { // last byte received isr_transfer_finish(); // releases SPI bus after last RXNE is set } break; case SPI_ISR_RXTX: *_recv_address++ = _desc.dev->SPIx->DR; _send_len--; if(!_send_len) { // readed the last byte isr_transfer_finish(); // releases SPI bus } break; case SPI_ISR_COMPARE: _recv_data = _desc.dev->SPIx->DR; #ifdef DEBUG_SPI p.data = _recv_data; p.cb = _compare_cb; #endif if(_compare_cb(_recv_data) ) { // ok if(_recv_len){ _isr_mode = SPI_ISR_RECEIVE; // we should receive after wait ? } else { _send_len=0; // mark transfer finished isr_transfer_finish(); } } else { // do nothing - just skip byte _desc.dev->SPIx->DR = _send_len; // data to send in len, only when we need next byte } break; case SPI_ISR_WAIT_RX_DMA: // we just got last RXNE of transfer (void)_desc.dev->SPIx->DR; // read fake data out if(_dummy_len){ _dummy_len--; } if(_dummy_len==0) { // this was a last dummy byte, now we should program DMA for receive or turn on ISR mode receiving if can't DMA // now we should set up DMA transfer bool can_dma = false; uint8_t *recv = _recv_address; uint16_t len = _recv_len; uint8_t nb = _desc.bus-1; delay_ns100(5); // small delay between TX and RX, to give the chip time to think over domestic affairs if(ADDRESS_IN_RAM(recv)){ //not in CCM _desc.dev->state->len=0; can_dma=true; }else if(len<=SPI_BUFFER_SIZE && buffer[nb]){ _desc.dev->state->len=len; _desc.dev->state->dst=recv; recv = buffer[nb]; can_dma=true; } if(can_dma){ #ifdef DEBUG_SPI p.act |= 10; #endif spi_disable_irq(_desc.dev, SPI_RXNE_INTERRUPT); // disable RXNE interrupt, TXE already disabled setup_dma_transfer(NULL, recv, len); start_dma_transfer(); _recv_len = 0; // all done _isr_mode = SPI_ISR_NONE; break; } } // no break! we can't receive via DMA so setup receive in ISR case SPI_ISR_WAIT_RX: // turn on ISR mode receiving (void)_desc.dev->SPIx->DR; // read fake data out if(_dummy_len){ _dummy_len--; } if(_dummy_len==0) { // this was a last dummy byte, now we should receive _isr_mode = SPI_ISR_RECEIVE; // we should receive _desc.dev->SPIx->DR = 0xFF; // write dummy byte for 1st transfer _dummy_len = _recv_len-1; // set number of bytes to be sent if(_dummy_len) { // if we should send additional bytes spi_enable_irq(_desc.dev, SPI_TXE_INTERRUPT); // enable TXE interrupt } } break; case SPI_ISR_FINISH: (void)_desc.dev->SPIx->DR; // read fake data out if(_dummy_len){ _dummy_len--; } if(_dummy_len) break; // not last byte case SPI_ISR_NONE: default: isr_transfer_finish(); // releases SPI bus break; } } }