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.
261 lines
7.4 KiB
261 lines
7.4 KiB
/// -*- tab-width: 4; Mode: C++; c-basic-offset: 4; indent-tabs-mode: nil -*- |
|
/* |
|
|
|
(c) 2017 night_ghost@ykoctpa.ru |
|
|
|
based on: |
|
|
|
* Copyright (C) 2015 Intel Corporation. All rights reserved. |
|
* |
|
* This file is free software: you can redistribute it and/or modify it |
|
* under the terms of the GNU General Public License as published by the |
|
* Free Software Foundation, either version 3 of the License, or |
|
* (at your option) any later version. |
|
* |
|
* This file is distributed in the hope that it will be useful, but |
|
* WITHOUT ANY WARRANTY; without even the implied warranty of |
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. |
|
* See the GNU General Public License for more details. |
|
* |
|
* You should have received a copy of the GNU General Public License along |
|
* with this program. If not, see <http://www.gnu.org/licenses/>. |
|
*/ |
|
#pragma once |
|
|
|
|
|
#include <AP_HAL/HAL.h> |
|
#include "Semaphores.h" |
|
#include "Scheduler.h" |
|
#include <dma.h> |
|
#include <spi.h> |
|
|
|
namespace F4Light { |
|
|
|
#define MAX_BUS_NUM 3 |
|
|
|
typedef enum SPIFrequency { |
|
SPI_18MHZ = 0, /**< 18 MHz */ |
|
SPI_9MHZ = 1, /**< 9 MHz */ |
|
SPI_4_5MHZ = 2, /**< 4.5 MHz */ |
|
SPI_2_25MHZ = 3, /**< 2.25 MHz */ |
|
SPI_1_125MHZ = 4, /**< 1.125 MHz */ |
|
SPI_562_500KHZ = 5, /**< 562.500 KHz */ |
|
SPI_281_250KHZ = 6, /**< 281.250 KHz */ |
|
SPI_140_625KHZ = 7, /**< 140.625 KHz */ |
|
SPI_36MHZ = 8, /**< 36 MHz */ |
|
} SPIFrequency; |
|
|
|
typedef uint8_t (*spi_WaitFunc)(uint8_t b); |
|
|
|
struct spi_pins { |
|
uint8_t sck; |
|
uint8_t miso; |
|
uint8_t mosi; |
|
}; |
|
|
|
typedef enum SPI_TRANSFER_MODE { |
|
SPI_TRANSFER_POLL=0, |
|
SPI_TRANSFER_DMA, |
|
SPI_TRANSFER_INTR, |
|
SPI_TRANSFER_SOFT, |
|
} SPI_transferMode; |
|
|
|
typedef struct SPIDESC { |
|
const char * const name; |
|
const spi_dev * const dev; |
|
uint8_t bus; |
|
spi_mode sm; |
|
uint16_t cs_pin; |
|
SPIFrequency lowspeed; |
|
SPIFrequency highspeed; |
|
SPI_transferMode mode; // mode of operations: 0 - polling, 1&2 DMA, 3 interrupts, 4 software |
|
uint32_t prio; // DMA priority |
|
uint8_t assert_dly; // delay after CS goes low |
|
uint8_t release_dly; // delay before CS goes high |
|
} SPIDesc; |
|
|
|
enum SPI_ISR_MODE { |
|
SPI_ISR_NONE, |
|
SPI_ISR_SEND, |
|
SPI_ISR_SEND_DMA, // want DMA but can't |
|
SPI_ISR_SKIP_RX, // wait for 1 dummy byte |
|
SPI_ISR_SKIP_RX_DMA, |
|
SPI_ISR_WAIT_RX, // wait for receiving of last fake byte from TX |
|
SPI_ISR_WAIT_RX_DMA, // wait for receiving of last fake byte from TX |
|
SPI_ISR_RECEIVE, |
|
SPI_ISR_RXTX, // full duplex |
|
SPI_ISR_STROBE, |
|
SPI_ISR_COMPARE, |
|
SPI_ISR_FINISH, |
|
}; |
|
|
|
//#define DEBUG_SPI |
|
|
|
#ifdef DEBUG_SPI |
|
// for debug |
|
typedef struct SPI_TRANS { |
|
const spi_dev * dev; |
|
uint32_t time; |
|
enum SPI_ISR_MODE mode; |
|
uint8_t act; |
|
uint8_t data; |
|
uint32_t cr2; |
|
uint32_t sr1; |
|
uint16_t send_len; |
|
uint16_t recv_len; |
|
uint16_t dummy_len; |
|
spi_WaitFunc cb; |
|
} spi_trans; |
|
|
|
#define SPI_LOG_SIZE 200 |
|
#endif |
|
|
|
class SPIDevice : public AP_HAL::SPIDevice { |
|
public: |
|
SPIDevice(const SPIDesc &device_desc); |
|
|
|
~SPIDevice() {} |
|
|
|
/* AP_HAL::SPIDevice implementation */ |
|
|
|
bool set_speed(AP_HAL::Device::Speed speed) override; |
|
|
|
bool transfer(const uint8_t *send, uint32_t send_len, |
|
uint8_t *recv, uint32_t recv_len) override; |
|
|
|
// one byte |
|
uint8_t transfer(uint8_t out); |
|
void send(uint8_t out); // without wait for answer |
|
|
|
/* See AP_HAL::SPIDevice::transfer_fullduplex() */ |
|
bool transfer_fullduplex(const uint8_t *send, uint8_t *recv, uint32_t len) override; |
|
|
|
/* single-byte transfers don't do it */ |
|
inline void apply_speed() { |
|
spi_set_speed(_desc.dev, determine_baud_rate(_speed)); |
|
} |
|
|
|
uint16_t send_strobe(const uint8_t *buffer, uint16_t len); // send in ISR and strobe each byte by CS |
|
void wait_busy() { spi_wait_busy(_desc.dev); } |
|
uint8_t wait_for(uint8_t out, spi_WaitFunc cb, uint32_t dly); // wait for needed byte in ISR |
|
|
|
/* See AP_HAL::Device::get_semaphore() */ |
|
inline F4Light::Semaphore *get_semaphore() { uint8_t n = _desc.bus - 1; if(n<MAX_BUS_NUM) { return &_semaphores[n];} else return NULL; } // numbers from 1 |
|
|
|
/* See AP_HAL::Device::register_periodic_callback() */ |
|
inline AP_HAL::Device::PeriodicHandle register_periodic_callback(uint32_t period_usec, AP_HAL::Device::PeriodicCb proc) override |
|
{ |
|
return Scheduler::register_timer_task(period_usec, proc, get_semaphore() ); |
|
} |
|
|
|
inline bool adjust_periodic_callback(AP_HAL::Device::PeriodicHandle h, uint32_t period_usec) override |
|
{ |
|
return Scheduler::adjust_timer_task(h, period_usec); |
|
} |
|
|
|
inline bool unregister_callback(PeriodicHandle h) { return Scheduler::unregister_timer_task(h); } |
|
|
|
void register_completion_callback(Handler h); |
|
|
|
inline void register_completion_callback(AP_HAL::MemberProc proc){ |
|
Revo_handler r = { .mp=proc }; |
|
register_completion_callback(r.h); |
|
} |
|
|
|
inline void register_completion_callback(AP_HAL::Proc proc){ |
|
Revo_handler r = { .hp=proc }; |
|
register_completion_callback(r.h); |
|
} |
|
|
|
void dma_isr(); |
|
void spi_isr(); |
|
|
|
#define SPI_BUFFER_SIZE 512 // one SD-card sector |
|
|
|
protected: |
|
const SPIDesc &_desc; |
|
|
|
DigitalSource *_cs; |
|
SPIFrequency _speed; |
|
|
|
static F4Light::Semaphore _semaphores[MAX_BUS_NUM]; // per bus |
|
static void * owner[MAX_BUS_NUM]; |
|
static uint8_t *buffer[MAX_BUS_NUM]; // array of pointers, allocate on 1st use |
|
|
|
bool _initialized; |
|
uint8_t byte_time; // in 0.25uS |
|
|
|
void init(void); |
|
|
|
inline void _cs_assert(){ if(_cs){_cs->write(0); delay_ns100(_desc.assert_dly); } } // Select device and wait a little |
|
inline void _cs_release(){ spi_wait_busy(_desc.dev); if(_cs){ delay_ns100(_desc.release_dly); _cs->write(1); } } // Deselect device after some delay |
|
|
|
const spi_pins* dev_to_spi_pins(const spi_dev *dev); |
|
|
|
spi_baud_rate determine_baud_rate(SPIFrequency freq); |
|
|
|
uint8_t _transfer(uint8_t data); |
|
|
|
void get_dma_ready(); |
|
|
|
void setup_dma_transfer(const uint8_t *send, const uint8_t *recv, uint32_t btr ); |
|
void setup_isr_transfer(); |
|
|
|
void start_dma_transfer(); |
|
uint8_t do_transfer(bool is_DMA, uint32_t nbytes); |
|
|
|
Handler _completion_cb; |
|
void *_task; |
|
|
|
|
|
// vars for send_strobe() and wait_for() |
|
const uint8_t *_send_address; |
|
uint16_t _send_len; |
|
uint16_t _dummy_len; |
|
uint8_t *_recv_address; |
|
uint16_t _recv_len; |
|
|
|
SPI_ISR_MODE _isr_mode; |
|
spi_WaitFunc _compare_cb; |
|
uint8_t _recv_data; |
|
|
|
void isr_transfer_finish(); |
|
void disable_dma(); |
|
|
|
#ifdef BOARD_SOFTWARE_SPI |
|
volatile GPIO_TypeDef *sck_port; |
|
uint16_t sck_pin; |
|
|
|
volatile GPIO_TypeDef *mosi_port; |
|
uint16_t mosi_pin; |
|
|
|
volatile GPIO_TypeDef *miso_port; |
|
uint16_t miso_pin; |
|
|
|
uint16_t dly_time; |
|
void spi_soft_set_speed(); |
|
uint8_t _transfer_s(uint8_t bt); |
|
|
|
#endif |
|
|
|
#ifdef DEBUG_SPI |
|
static spi_trans spi_trans_array[SPI_LOG_SIZE]; |
|
static uint8_t spi_trans_ptr; |
|
#endif |
|
|
|
}; |
|
|
|
class SPIDeviceManager : public AP_HAL::SPIDeviceManager { |
|
public: |
|
friend class SPIDevice; |
|
|
|
SPIDeviceManager(){ } |
|
|
|
AP_HAL::OwnPtr<AP_HAL::SPIDevice> get_device(const char *name) { return _get_device(name); } |
|
static AP_HAL::OwnPtr<F4Light::SPIDevice> _get_device(const char *name); |
|
|
|
protected: |
|
|
|
}; |
|
|
|
} // namespace
|
|
|