Этот коммит содержится в:
Ayke van Laethem 2020-09-13 14:51:31 +02:00 коммит произвёл Ron Evans
родитель a9a6d0ee63
коммит 5b81b835ba

Просмотреть файл

@ -4,7 +4,9 @@ package machine
import ( import (
"device/esp" "device/esp"
"errors"
"runtime/volatile" "runtime/volatile"
"unsafe"
) )
const peripheralClock = 80000000 // 80MHz const peripheralClock = 80000000 // 80MHz
@ -15,6 +17,10 @@ func CPUFrequency() uint32 {
return 160e6 // 160MHz return 160e6 // 160MHz
} }
var (
ErrInvalidSPIBus = errors.New("machine: invalid SPI bus")
)
type PinMode uint8 type PinMode uint8
const ( const (
@ -26,6 +32,24 @@ const (
// Configure this pin with the given configuration. // Configure this pin with the given configuration.
func (p Pin) Configure(config PinConfig) { func (p Pin) Configure(config PinConfig) {
// Output function 256 is a special value reserved for use as a regular GPIO
// pin. Peripherals (SPI etc) can set a custom output function by calling
// lowercase configure() instead with a signal name.
p.configure(config, 256)
}
// configure is the same as Configure, but allows for setting a specific input
// or output signal.
// Signals are always routed through the GPIO matrix for simplicity. Output
// signals are configured in FUNCx_OUT_SEL_CFG which selects a particular signal
// to output on a given pin. Input signals are configured in FUNCy_IN_SEL_CFG,
// which sets the pin to use for a particular input signal.
func (p Pin) configure(config PinConfig, signal uint32) {
if p == NoPin {
// This simplifies pin configuration in peripherals such as SPI.
return
}
var muxConfig uint32 // The mux configuration. var muxConfig uint32 // The mux configuration.
// Configure this pin as a GPIO pin. // Configure this pin as a GPIO pin.
@ -56,6 +80,10 @@ func (p Pin) Configure(config PinConfig) {
} else { } else {
esp.GPIO.ENABLE1_W1TS.Set(1 << (p - 32)) esp.GPIO.ENABLE1_W1TS.Set(1 << (p - 32))
} }
// Set the signal to read the output value from. It can be a peripheral
// output signal, or the special value 256 which indicates regular GPIO
// usage.
p.outFunc().Set(signal)
case PinInput, PinInputPullup, PinInputPulldown: case PinInput, PinInputPullup, PinInputPulldown:
// Clear the 'output enable' bit. // Clear the 'output enable' bit.
if p < 32 { if p < 32 {
@ -63,9 +91,29 @@ func (p Pin) Configure(config PinConfig) {
} else { } else {
esp.GPIO.ENABLE1_W1TC.Set(1 << (p - 32)) esp.GPIO.ENABLE1_W1TC.Set(1 << (p - 32))
} }
if signal != 256 {
// Signal is a peripheral function (not a simple GPIO). Connect this
// signal to the pin.
// Note that outFunc and inFunc work in the opposite direction.
// outFunc configures a pin to use a given output signal, while
// inFunc specifies a pin to use to read the signal from.
inFunc(signal).Set(esp.GPIO_FUNC_IN_SEL_CFG_SEL | uint32(p)<<esp.GPIO_FUNC_IN_SEL_CFG_IN_SEL_Pos)
}
} }
} }
// outFunc returns the FUNCx_OUT_SEL_CFG register used for configuring the
// output function selection.
func (p Pin) outFunc() *volatile.Register32 {
return (*volatile.Register32)(unsafe.Pointer((uintptr(unsafe.Pointer(&esp.GPIO.FUNC0_OUT_SEL_CFG)) + uintptr(p)*4)))
}
// inFunc returns the FUNCy_IN_SEL_CFG register used for configuring the input
// function selection.
func inFunc(signal uint32) *volatile.Register32 {
return (*volatile.Register32)(unsafe.Pointer((uintptr(unsafe.Pointer(&esp.GPIO.FUNC0_IN_SEL_CFG)) + uintptr(signal)*4)))
}
// Set the pin to high or low. // Set the pin to high or low.
// Warning: only use this on an output pin! // Warning: only use this on an output pin!
func (p Pin) Set(value bool) { func (p Pin) Set(value bool) {
@ -232,3 +280,224 @@ func (uart UART) WriteByte(b byte) error {
uart.Bus.TX_FIFO.Set(b) uart.Bus.TX_FIFO.Set(b)
return nil return nil
} }
// Serial Peripheral Interface on the ESP32.
type SPI struct {
Bus *esp.SPI_Type
}
var (
// SPI0 and SPI1 are reserved for use by the caching system etc.
SPI2 = SPI{esp.SPI2}
SPI3 = SPI{esp.SPI3}
)
// SPIConfig configures a SPI peripheral on the ESP32. Make sure to set at least
// SCK, SDO and SDI (possibly to NoPin if not in use). The default for LSBFirst
// (false) and Mode (0) are good for most applications. The frequency defaults
// to 1MHz if not set but can be configured up to 40MHz. Possible values are
// 40MHz and integer divisions from 40MHz such as 20MHz, 13.3MHz, 10MHz, 8MHz,
// etc.
type SPIConfig struct {
Frequency uint32
SCK Pin
SDO Pin
SDI Pin
LSBFirst bool
Mode uint8
}
// Configure and make the SPI peripheral ready to use.
func (spi SPI) Configure(config SPIConfig) error {
if config.Frequency == 0 {
config.Frequency = 1e6 // default to 1MHz
}
// Configure the SPI clock. This assumes a peripheral clock of 80MHz.
var clockReg uint32
if config.Frequency >= 40e6 {
// Don't use a prescaler, but directly connect to the APB clock. This
// results in a SPI clock frequency of 40MHz.
clockReg |= esp.SPI_CLOCK_CLK_EQU_SYSCLK
} else {
// Use a prescaler for frequencies below 40MHz. They will get rounded
// down to the next possible frequency (20MHz, 13.3MHz, 10MHz, 8MHz,
// 6.7MHz, 5.7MHz, 5MHz, etc).
// This code is much simpler than how ESP-IDF configures the frequency,
// but should be just as accurate. The only exception is for frequencies
// below 4883Hz, which will need special support.
if config.Frequency < 4883 {
// The current lower limit is 4883Hz.
// The hardware supports lower frequencies by setting the h and n
// variables, but that's not yet implemented.
config.Frequency = 4883
}
// The prescaler value is 40e6 / config.Frequency, but rounded up so
// that the actual frequency is never higher than the frequency
// requested in config.Frequency.
var (
pre uint32 = (40e6 + config.Frequency - 1) / config.Frequency
n uint32 = 2 // this value seems to equal the number of ticks per SPI clock tick
h uint32 = 1 // must be half of n according to the formula in the reference manual
l uint32 = n // must equal n according to the reference manual
)
clockReg |= (pre - 1) << esp.SPI_CLOCK_CLKDIV_PRE_Pos
clockReg |= (n - 1) << esp.SPI_CLOCK_CLKCNT_N_Pos
clockReg |= (h - 1) << esp.SPI_CLOCK_CLKCNT_H_Pos
clockReg |= (l - 1) << esp.SPI_CLOCK_CLKCNT_L_Pos
}
spi.Bus.CLOCK.Set(clockReg)
// SPI_CTRL_REG controls bit order.
var ctrlReg uint32
if config.LSBFirst {
ctrlReg |= esp.SPI_CTRL_WR_BIT_ORDER
ctrlReg |= esp.SPI_CTRL_RD_BIT_ORDER
}
spi.Bus.CTRL.Set(ctrlReg)
// SPI_CTRL2_REG, SPI_USER_REG and SPI_PIN_REG control SPI clock polarity
// (mode), among others.
var ctrl2Reg, userReg, pinReg uint32
// For mode configuration, see table 29 in the reference manual (page 128).
var delayMode uint32
switch config.Mode {
case 0:
delayMode = 2
case 1:
delayMode = 1
userReg |= esp.SPI_USER_CK_OUT_EDGE
case 2:
delayMode = 1
userReg |= esp.SPI_USER_CK_OUT_EDGE
pinReg |= esp.SPI_PIN_CK_IDLE_EDGE
case 3:
delayMode = 2
pinReg |= esp.SPI_PIN_CK_IDLE_EDGE
}
// Extra configuration necessary for correct data input at high frequencies.
// This is only necessary when MISO goes through the GPIO matrix (which it
// currently does).
if config.Frequency >= 40e6 {
// Delay mode must be set to 0 and SPI_USR_DUMMY_CYCLELEN should be set
// to 0 (the default).
userReg |= esp.SPI_USER_USR_DUMMY
} else if config.Frequency >= 20e6 {
// Nothing to do here, delay mode should be set to 0 according to the
// datasheet.
} else {
// Follow the delay mode as given in table 29 on page 128 of the
// reference manual.
// Note that this is only specified for SPI frequency of 10MHz and
// below (≤Fapb/8), so 13.3MHz appears to be left unspecified.
ctrl2Reg |= delayMode << esp.SPI_CTRL2_MOSI_DELAY_MODE_Pos
}
// Enable full-duplex communication.
userReg |= esp.SPI_USER_DOUTDIN
userReg |= esp.SPI_USER_USR_MOSI
// Write values to registers.
spi.Bus.CTRL2.Set(ctrl2Reg)
spi.Bus.USER.Set(userReg)
spi.Bus.PIN.Set(pinReg)
// Configure pins.
// TODO: use direct output if possible, if the configured pins match the
// possible direct configurations (e.g. for SPI2, when SCK is pin 14 etc).
if spi.Bus == esp.SPI2 {
config.SCK.configure(PinConfig{Mode: PinOutput}, 8) // HSPICLK
config.SDI.configure(PinConfig{Mode: PinInput}, 9) // HSPIQ
config.SDO.configure(PinConfig{Mode: PinOutput}, 10) // HSPID
} else if spi.Bus == esp.SPI3 {
config.SCK.configure(PinConfig{Mode: PinOutput}, 63) // VSPICLK
config.SDI.configure(PinConfig{Mode: PinInput}, 64) // VSPIQ
config.SDO.configure(PinConfig{Mode: PinOutput}, 65) // VSPID
} else {
// Don't know how to configure this bus.
return ErrInvalidSPIBus
}
return nil
}
// Transfer writes/reads a single byte using the SPI interface. If you need to
// transfer larger amounts of data, Tx will be faster.
func (spi SPI) Transfer(w byte) (byte, error) {
spi.Bus.MISO_DLEN.Set(7 << esp.SPI_MISO_DLEN_USR_MISO_DBITLEN_Pos)
spi.Bus.MOSI_DLEN.Set(7 << esp.SPI_MOSI_DLEN_USR_MOSI_DBITLEN_Pos)
spi.Bus.W0.Set(uint32(w))
// Send/receive byte.
spi.Bus.CMD.Set(esp.SPI_CMD_USR)
for spi.Bus.CMD.Get() != 0 {
}
// The received byte is stored in W0.
return byte(spi.Bus.W0.Get()), nil
}
// Tx handles read/write operation for SPI interface. Since SPI is a syncronous write/read
// interface, there must always be the same number of bytes written as bytes read.
// This is accomplished by sending zero bits if r is bigger than w or discarding
// the incoming data if w is bigger than r.
//
func (spi SPI) Tx(w, r []byte) error {
toTransfer := len(w)
if len(r) > toTransfer {
toTransfer = len(r)
}
for toTransfer != 0 {
// Do only 64 bytes at a time.
chunkSize := toTransfer
if chunkSize > 64 {
chunkSize = 64
}
// Fill tx buffer.
transferWords := (*[16]volatile.Register32)(unsafe.Pointer(uintptr(unsafe.Pointer(&spi.Bus.W0))))
var outBuf [16]uint32
txSize := 64
if txSize > len(w) {
txSize = len(w)
}
for i := 0; i < txSize; i++ {
outBuf[i/4] = outBuf[i/4] | uint32(w[i])<<((i%4)*8)
}
for i, word := range outBuf {
transferWords[i].Set(word)
}
// Do the transfer.
spi.Bus.MISO_DLEN.Set((uint32(chunkSize)*8 - 1) << esp.SPI_MISO_DLEN_USR_MISO_DBITLEN_Pos)
spi.Bus.MOSI_DLEN.Set((uint32(chunkSize)*8 - 1) << esp.SPI_MOSI_DLEN_USR_MOSI_DBITLEN_Pos)
spi.Bus.CMD.Set(esp.SPI_CMD_USR)
for spi.Bus.CMD.Get() != 0 {
}
// Read rx buffer.
rxSize := 64
if rxSize > len(r) {
rxSize = len(r)
}
for i := 0; i < rxSize; i++ {
r[i] = byte(transferWords[i/4].Get() >> ((i % 4) * 8))
}
// Cut off some part of the output buffer so the next iteration we will
// only send the remaining bytes.
if len(w) < chunkSize {
w = nil
} else {
w = w[chunkSize:]
}
if len(r) < chunkSize {
r = nil
} else {
r = r[chunkSize:]
}
toTransfer -= chunkSize
}
return nil
}