|
|
|
/*
|
|
|
|
* The MIT License (MIT)
|
|
|
|
*
|
|
|
|
* Copyright (c) 2015 David Ogilvy (MetalPhreak)
|
|
|
|
* Modified 2016 by Radomir Dopieralski
|
|
|
|
*
|
|
|
|
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
|
|
* of this software and associated documentation files (the "Software"), to deal
|
|
|
|
* in the Software without restriction, including without limitation the rights
|
|
|
|
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
|
|
* copies of the Software, and to permit persons to whom the Software is
|
|
|
|
* furnished to do so, subject to the following conditions:
|
|
|
|
*
|
|
|
|
* The above copyright notice and this permission notice shall be included in all
|
|
|
|
* copies or substantial portions of the Software.
|
|
|
|
*
|
|
|
|
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
|
|
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
|
|
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
|
|
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
|
|
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
|
|
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
|
|
* SOFTWARE.
|
|
|
|
*/
|
|
|
|
|
|
|
|
#include "hspi.h"
|
|
|
|
|
|
|
|
/*
|
|
|
|
Wrapper to setup HSPI/SPI GPIO pins and default SPI clock
|
|
|
|
spi_no - SPI (0) or HSPI (1)
|
|
|
|
Not used in MicroPython.
|
|
|
|
*/
|
|
|
|
void spi_init(uint8_t spi_no) {
|
|
|
|
spi_init_gpio(spi_no, SPI_CLK_USE_DIV);
|
|
|
|
spi_clock(spi_no, SPI_CLK_PREDIV, SPI_CLK_CNTDIV);
|
|
|
|
spi_tx_byte_order(spi_no, SPI_BYTE_ORDER_HIGH_TO_LOW);
|
|
|
|
spi_rx_byte_order(spi_no, SPI_BYTE_ORDER_HIGH_TO_LOW);
|
|
|
|
|
|
|
|
SET_PERI_REG_MASK(SPI_USER(spi_no), SPI_CS_SETUP | SPI_CS_HOLD);
|
|
|
|
CLEAR_PERI_REG_MASK(SPI_USER(spi_no), SPI_FLASH_MODE);
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/*
|
|
|
|
Configures SPI mode parameters for clock edge and clock polarity.
|
|
|
|
spi_no - SPI (0) or HSPI (1)
|
|
|
|
spi_cpha - (0) Data is valid on clock leading edge
|
|
|
|
(1) Data is valid on clock trailing edge
|
|
|
|
spi_cpol - (0) Clock is low when inactive
|
|
|
|
(1) Clock is high when inactive
|
|
|
|
For MicroPython this version is different from original.
|
|
|
|
*/
|
|
|
|
void spi_mode(uint8_t spi_no, uint8_t spi_cpha, uint8_t spi_cpol) {
|
|
|
|
if (spi_cpol) {
|
|
|
|
SET_PERI_REG_MASK(SPI_PIN(HSPI), SPI_IDLE_EDGE);
|
|
|
|
} else {
|
|
|
|
CLEAR_PERI_REG_MASK(SPI_PIN(HSPI), SPI_IDLE_EDGE);
|
|
|
|
}
|
|
|
|
if (spi_cpha == spi_cpol) {
|
|
|
|
// Mode 3 - MOSI is set on falling edge of clock
|
|
|
|
// Mode 0 - MOSI is set on falling edge of clock
|
|
|
|
CLEAR_PERI_REG_MASK(SPI_USER(HSPI), SPI_CK_OUT_EDGE);
|
|
|
|
SET_PERI_REG_MASK(SPI_USER(HSPI), SPI_CK_I_EDGE);
|
|
|
|
} else {
|
|
|
|
// Mode 2 - MOSI is set on rising edge of clock
|
|
|
|
// Mode 1 - MOSI is set on rising edge of clock
|
|
|
|
SET_PERI_REG_MASK(SPI_USER(HSPI), SPI_CK_OUT_EDGE);
|
|
|
|
CLEAR_PERI_REG_MASK(SPI_USER(HSPI), SPI_CK_I_EDGE);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/*
|
|
|
|
Initialise the GPIO pins for use as SPI pins.
|
|
|
|
spi_no - SPI (0) or HSPI (1)
|
|
|
|
sysclk_as_spiclk -
|
|
|
|
SPI_CLK_80MHZ_NODIV (1) if using 80MHz for SPI clock.
|
|
|
|
SPI_CLK_USE_DIV (0) if using divider for lower speed.
|
|
|
|
*/
|
|
|
|
void spi_init_gpio(uint8_t spi_no, uint8_t sysclk_as_spiclk) {
|
|
|
|
uint32_t clock_div_flag = 0;
|
|
|
|
if (sysclk_as_spiclk) {
|
|
|
|
clock_div_flag = 0x0001;
|
|
|
|
}
|
|
|
|
if (spi_no == SPI) {
|
|
|
|
// Set bit 8 if 80MHz sysclock required
|
|
|
|
WRITE_PERI_REG(PERIPHS_IO_MUX, 0x005 | (clock_div_flag << 8));
|
|
|
|
PIN_FUNC_SELECT(PERIPHS_IO_MUX_SD_CLK_U, 1);
|
|
|
|
PIN_FUNC_SELECT(PERIPHS_IO_MUX_SD_CMD_U, 1);
|
|
|
|
PIN_FUNC_SELECT(PERIPHS_IO_MUX_SD_DATA0_U, 1);
|
|
|
|
PIN_FUNC_SELECT(PERIPHS_IO_MUX_SD_DATA1_U, 1);
|
|
|
|
} else if (spi_no == HSPI) {
|
|
|
|
// Set bit 9 if 80MHz sysclock required
|
|
|
|
WRITE_PERI_REG(PERIPHS_IO_MUX, 0x105 | (clock_div_flag << 9));
|
|
|
|
// GPIO12 is HSPI MISO pin (Master Data In)
|
|
|
|
PIN_FUNC_SELECT(PERIPHS_IO_MUX_MTDI_U, 2);
|
|
|
|
// GPIO13 is HSPI MOSI pin (Master Data Out)
|
|
|
|
PIN_FUNC_SELECT(PERIPHS_IO_MUX_MTCK_U, 2);
|
|
|
|
// GPIO14 is HSPI CLK pin (Clock)
|
|
|
|
PIN_FUNC_SELECT(PERIPHS_IO_MUX_MTMS_U, 2);
|
|
|
|
// GPIO15 is HSPI CS pin (Chip Select / Slave Select)
|
|
|
|
// In MicroPython, we are handling CS ourself in drivers.
|
|
|
|
// PIN_FUNC_SELECT(PERIPHS_IO_MUX_MTDO_U, 2);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/*
|
|
|
|
Set up the control registers for the SPI clock
|
|
|
|
spi_no - SPI (0) or HSPI (1)
|
|
|
|
prediv - predivider value (actual division value)
|
|
|
|
cntdiv - postdivider value (actual division value)
|
|
|
|
Set either divider to 0 to disable all division (80MHz sysclock)
|
|
|
|
*/
|
|
|
|
void spi_clock(uint8_t spi_no, uint16_t prediv, uint8_t cntdiv) {
|
|
|
|
if (prediv == 0 || cntdiv == 0) {
|
|
|
|
WRITE_PERI_REG(SPI_CLOCK(spi_no), SPI_CLK_EQU_SYSCLK);
|
|
|
|
} else {
|
|
|
|
WRITE_PERI_REG(SPI_CLOCK(spi_no),
|
|
|
|
(((prediv - 1) & SPI_CLKDIV_PRE) << SPI_CLKDIV_PRE_S) |
|
|
|
|
(((cntdiv - 1) & SPI_CLKCNT_N) << SPI_CLKCNT_N_S) |
|
|
|
|
(((cntdiv >> 1) & SPI_CLKCNT_H) << SPI_CLKCNT_H_S) |
|
|
|
|
((0 & SPI_CLKCNT_L) << SPI_CLKCNT_L_S)
|
|
|
|
);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/*
|
|
|
|
Setup the byte order for shifting data out of buffer
|
|
|
|
spi_no - SPI (0) or HSPI (1)
|
|
|
|
byte_order -
|
|
|
|
SPI_BYTE_ORDER_HIGH_TO_LOW (1)
|
|
|
|
Data is sent out starting with Bit31 and down to Bit0
|
|
|
|
SPI_BYTE_ORDER_LOW_TO_HIGH (0)
|
|
|
|
Data is sent out starting with the lowest BYTE, from MSB to LSB,
|
|
|
|
followed by the second lowest BYTE, from MSB to LSB, followed by
|
|
|
|
the second highest BYTE, from MSB to LSB, followed by the highest
|
|
|
|
BYTE, from MSB to LSB 0xABCDEFGH would be sent as 0xGHEFCDAB.
|
|
|
|
*/
|
|
|
|
void spi_tx_byte_order(uint8_t spi_no, uint8_t byte_order) {
|
|
|
|
if (byte_order) {
|
|
|
|
SET_PERI_REG_MASK(SPI_USER(spi_no), SPI_WR_BYTE_ORDER);
|
|
|
|
} else {
|
|
|
|
CLEAR_PERI_REG_MASK(SPI_USER(spi_no), SPI_WR_BYTE_ORDER);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/*
|
|
|
|
Setup the byte order for shifting data into buffer
|
|
|
|
spi_no - SPI (0) or HSPI (1)
|
|
|
|
byte_order -
|
|
|
|
SPI_BYTE_ORDER_HIGH_TO_LOW (1)
|
|
|
|
Data is read in starting with Bit31 and down to Bit0
|
|
|
|
SPI_BYTE_ORDER_LOW_TO_HIGH (0)
|
|
|
|
Data is read in starting with the lowest BYTE, from MSB to LSB,
|
|
|
|
followed by the second lowest BYTE, from MSB to LSB, followed by
|
|
|
|
the second highest BYTE, from MSB to LSB, followed by the highest
|
|
|
|
BYTE, from MSB to LSB 0xABCDEFGH would be read as 0xGHEFCDAB
|
|
|
|
*/
|
|
|
|
void spi_rx_byte_order(uint8_t spi_no, uint8_t byte_order) {
|
|
|
|
if (byte_order) {
|
|
|
|
SET_PERI_REG_MASK(SPI_USER(spi_no), SPI_RD_BYTE_ORDER);
|
|
|
|
} else {
|
|
|
|
CLEAR_PERI_REG_MASK(SPI_USER(spi_no), SPI_RD_BYTE_ORDER);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/*
|
|
|
|
SPI transaction function
|
|
|
|
spi_no - SPI (0) or HSPI (1)
|
|
|
|
cmd_bits - actual number of bits to transmit
|
|
|
|
cmd_data - command data
|
|
|
|
addr_bits - actual number of bits to transmit
|
|
|
|
addr_data - address data
|
|
|
|
dout_bits - actual number of bits to transmit
|
|
|
|
dout_data - output data
|
|
|
|
din_bits - actual number of bits to receive
|
|
|
|
Returns: read data - uint32_t containing read in data only if RX was set
|
|
|
|
0 - something went wrong (or actual read data was 0)
|
|
|
|
1 - data sent ok (or actual read data is 1)
|
|
|
|
Note: all data is assumed to be stored in the lower bits of the data variables
|
|
|
|
(for anything <32 bits).
|
|
|
|
*/
|
|
|
|
uint32_t spi_transaction(uint8_t spi_no, uint8_t cmd_bits, uint16_t cmd_data,
|
|
|
|
uint32_t addr_bits, uint32_t addr_data,
|
|
|
|
uint32_t dout_bits, uint32_t dout_data,
|
|
|
|
uint32_t din_bits, uint32_t dummy_bits) {
|
|
|
|
while (spi_busy(spi_no)) {
|
|
|
|
}
|
|
|
|
; // Wait for SPI to be ready
|
|
|
|
|
|
|
|
// Enable SPI Functions
|
|
|
|
// Disable MOSI, MISO, ADDR, COMMAND, DUMMY in case previously set.
|
|
|
|
CLEAR_PERI_REG_MASK(SPI_USER(spi_no), SPI_USR_MOSI | SPI_USR_MISO |
|
|
|
|
SPI_USR_COMMAND | SPI_USR_ADDR | SPI_USR_DUMMY);
|
|
|
|
|
|
|
|
// Enable functions based on number of bits. 0 bits = disabled.
|
|
|
|
// This is rather inefficient but allows for a very generic function.
|
|
|
|
// CMD ADDR and MOSI are set below to save on an extra if statement.
|
|
|
|
if (din_bits) {
|
|
|
|
if (dout_bits) {
|
|
|
|
SET_PERI_REG_MASK(SPI_USER(spi_no), SPI_DOUTDIN);
|
|
|
|
} else {
|
|
|
|
SET_PERI_REG_MASK(SPI_USER(spi_no), SPI_USR_MISO);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
if (dummy_bits) {
|
|
|
|
SET_PERI_REG_MASK(SPI_USER(spi_no), SPI_USR_DUMMY);
|
|
|
|
}
|
|
|
|
|
|
|
|
// Setup Bitlengths
|
|
|
|
WRITE_PERI_REG(SPI_USER1(spi_no),
|
|
|
|
// Number of bits in Address
|
|
|
|
((addr_bits - 1) & SPI_USR_ADDR_BITLEN) << SPI_USR_ADDR_BITLEN_S |
|
|
|
|
// Number of bits to Send
|
|
|
|
((dout_bits - 1) & SPI_USR_MOSI_BITLEN) << SPI_USR_MOSI_BITLEN_S |
|
|
|
|
// Number of bits to receive
|
|
|
|
((din_bits - 1) & SPI_USR_MISO_BITLEN) << SPI_USR_MISO_BITLEN_S |
|
|
|
|
// Number of Dummy bits to insert
|
|
|
|
((dummy_bits - 1) & SPI_USR_DUMMY_CYCLELEN) << SPI_USR_DUMMY_CYCLELEN_S);
|
|
|
|
|
|
|
|
// Setup Command Data
|
|
|
|
if (cmd_bits) {
|
|
|
|
// Enable COMMAND function in SPI module
|
|
|
|
SET_PERI_REG_MASK(SPI_USER(spi_no), SPI_USR_COMMAND);
|
|
|
|
// Align command data to high bits
|
|
|
|
uint16_t command = cmd_data << (16 - cmd_bits);
|
|
|
|
// Swap byte order
|
|
|
|
command = ((command >> 8) & 0xff) | ((command << 8) & 0xff00);
|
|
|
|
WRITE_PERI_REG(SPI_USER2(spi_no), (
|
|
|
|
(((cmd_bits - 1) & SPI_USR_COMMAND_BITLEN) << SPI_USR_COMMAND_BITLEN_S) |
|
|
|
|
(command & SPI_USR_COMMAND_VALUE)
|
|
|
|
));
|
|
|
|
}
|
|
|
|
|
|
|
|
// Setup Address Data
|
|
|
|
if (addr_bits) {
|
|
|
|
// Enable ADDRess function in SPI module
|
|
|
|
SET_PERI_REG_MASK(SPI_USER(spi_no), SPI_USR_ADDR);
|
|
|
|
// Align address data to high bits
|
|
|
|
WRITE_PERI_REG(SPI_ADDR(spi_no), addr_data << (32 - addr_bits));
|
|
|
|
}
|
|
|
|
|
|
|
|
// Setup DOUT data
|
|
|
|
if (dout_bits) {
|
|
|
|
// Enable MOSI function in SPI module
|
|
|
|
SET_PERI_REG_MASK(SPI_USER(spi_no), SPI_USR_MOSI);
|
|
|
|
// Copy data to W0
|
|
|
|
if (READ_PERI_REG(SPI_USER(spi_no)) & SPI_WR_BYTE_ORDER) {
|
|
|
|
WRITE_PERI_REG(SPI_W0(spi_no), dout_data << (32 - dout_bits));
|
|
|
|
} else {
|
|
|
|
uint8_t dout_extra_bits = dout_bits % 8;
|
|
|
|
|
|
|
|
if (dout_extra_bits) {
|
|
|
|
// If your data isn't a byte multiple (8/16/24/32 bits) and you
|
|
|
|
// don't have SPI_WR_BYTE_ORDER set, you need this to move the
|
|
|
|
// non-8bit remainder to the MSBs. Not sure if there's even a use
|
|
|
|
// case for this, but it's here if you need it... For example,
|
|
|
|
// 0xDA4 12 bits without SPI_WR_BYTE_ORDER would usually be output
|
|
|
|
// as if it were 0x0DA4, of which 0xA4, and then 0x0 would be
|
|
|
|
// shifted out (first 8 bits of low byte, then 4 MSB bits of high
|
|
|
|
// byte - ie reverse byte order).
|
|
|
|
// The code below shifts it out as 0xA4 followed by 0xD as you
|
|
|
|
// might require.
|
|
|
|
WRITE_PERI_REG(SPI_W0(spi_no), (
|
|
|
|
(0xFFFFFFFF << (dout_bits - dout_extra_bits) & dout_data)
|
|
|
|
<< (8 - dout_extra_bits) |
|
|
|
|
((0xFFFFFFFF >> (32 - (dout_bits - dout_extra_bits)))
|
|
|
|
& dout_data)
|
|
|
|
));
|
|
|
|
} else {
|
|
|
|
WRITE_PERI_REG(SPI_W0(spi_no), dout_data);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// Begin SPI Transaction
|
|
|
|
SET_PERI_REG_MASK(SPI_CMD(spi_no), SPI_USR);
|
|
|
|
|
|
|
|
// Return DIN data
|
|
|
|
if (din_bits) {
|
|
|
|
while (spi_busy(spi_no)) {
|
|
|
|
}
|
|
|
|
; // Wait for SPI transaction to complete
|
|
|
|
if (READ_PERI_REG(SPI_USER(spi_no)) & SPI_RD_BYTE_ORDER) {
|
|
|
|
// Assuming data in is written to MSB. TBC
|
|
|
|
return READ_PERI_REG(SPI_W0(spi_no)) >> (32 - din_bits);
|
|
|
|
} else {
|
|
|
|
// Read in the same way as DOUT is sent. Note existing contents of
|
|
|
|
// SPI_W0 remain unless overwritten!
|
|
|
|
return READ_PERI_REG(SPI_W0(spi_no));
|
|
|
|
}
|
|
|
|
return 0; // Something went wrong
|
|
|
|
}
|
|
|
|
|
|
|
|
// Transaction completed
|
|
|
|
return 1; // Success
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/*
|
|
|
|
Just do minimal work needed to send 8 bits.
|
|
|
|
*/
|
|
|
|
inline void spi_tx8fast(uint8_t spi_no, uint8_t dout_data) {
|
|
|
|
while (spi_busy(spi_no)) {
|
|
|
|
}
|
|
|
|
; // Wait for SPI to be ready
|
|
|
|
|
|
|
|
// Enable SPI Functions
|
|
|
|
// Disable MOSI, MISO, ADDR, COMMAND, DUMMY in case previously set.
|
|
|
|
CLEAR_PERI_REG_MASK(SPI_USER(spi_no), SPI_USR_MOSI | SPI_USR_MISO |
|
|
|
|
SPI_USR_COMMAND | SPI_USR_ADDR | SPI_USR_DUMMY);
|
|
|
|
|
|
|
|
// Setup Bitlengths
|
|
|
|
WRITE_PERI_REG(SPI_USER1(spi_no),
|
|
|
|
// Number of bits to Send
|
|
|
|
((8 - 1) & SPI_USR_MOSI_BITLEN) << SPI_USR_MOSI_BITLEN_S |
|
|
|
|
// Number of bits to receive
|
|
|
|
((8 - 1) & SPI_USR_MISO_BITLEN) << SPI_USR_MISO_BITLEN_S);
|
|
|
|
|
|
|
|
|
|
|
|
// Setup DOUT data
|
|
|
|
// Enable MOSI function in SPI module
|
|
|
|
SET_PERI_REG_MASK(SPI_USER(spi_no), SPI_USR_MOSI);
|
|
|
|
// Copy data to W0
|
|
|
|
if (READ_PERI_REG(SPI_USER(spi_no)) & SPI_WR_BYTE_ORDER) {
|
|
|
|
WRITE_PERI_REG(SPI_W0(spi_no), dout_data << (32 - 8));
|
|
|
|
} else {
|
|
|
|
WRITE_PERI_REG(SPI_W0(spi_no), dout_data);
|
|
|
|
}
|
|
|
|
|
|
|
|
// Begin SPI Transaction
|
|
|
|
SET_PERI_REG_MASK(SPI_CMD(spi_no), SPI_USR);
|
|
|
|
}
|