feather-stm32f405: add SPI support (#1377)
* machine/stm32f405: add SPI support
Этот коммит содержится в:
родитель
431e51b8a0
коммит
9ad2315079
3 изменённых файлов: 136 добавлений и 42 удалений
|
@ -178,6 +178,22 @@ const (
|
||||||
SPI_SDO_PIN = SPI0_SDO_PIN //
|
SPI_SDO_PIN = SPI0_SDO_PIN //
|
||||||
)
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
SPI1 = SPI{
|
||||||
|
Bus: stm32.SPI2,
|
||||||
|
AltFuncSelector: stm32.AF5_SPI1_SPI2,
|
||||||
|
}
|
||||||
|
SPI2 = SPI{
|
||||||
|
Bus: stm32.SPI3,
|
||||||
|
AltFuncSelector: stm32.AF6_SPI3,
|
||||||
|
}
|
||||||
|
SPI3 = SPI{
|
||||||
|
Bus: stm32.SPI1,
|
||||||
|
AltFuncSelector: stm32.AF5_SPI1_SPI2,
|
||||||
|
}
|
||||||
|
SPI0 = SPI1
|
||||||
|
)
|
||||||
|
|
||||||
func initSPI() {}
|
func initSPI() {}
|
||||||
|
|
||||||
// -- I2C ----------------------------------------------------------------------
|
// -- I2C ----------------------------------------------------------------------
|
||||||
|
|
|
@ -20,15 +20,42 @@ type SPIConfig struct {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Configure is intended to setup the STM32 SPI1 interface.
|
// Configure is intended to setup the STM32 SPI1 interface.
|
||||||
// Features still TODO:
|
|
||||||
// - support SPI2 and SPI3
|
|
||||||
// - allow setting data size to 16 bits?
|
|
||||||
// - allow setting direction in HW for additional optimization?
|
|
||||||
// - hardware SS pin?
|
|
||||||
func (spi SPI) Configure(config SPIConfig) {
|
func (spi SPI) Configure(config SPIConfig) {
|
||||||
|
|
||||||
|
// -- CONFIGURING THE SPI IN MASTER MODE --
|
||||||
|
//
|
||||||
|
// 1. Select the BR[2:0] bits to define the serial clock baud rate (see
|
||||||
|
// SPI_CR1 register).
|
||||||
|
// 2. Select the CPOL and CPHA bits to define one of the four relationships
|
||||||
|
// between the data transfer and the serial clock (see Figure 248). This
|
||||||
|
// step is not required when the TI mode is selected.
|
||||||
|
// 3. Set the DFF bit to define 8- or 16-bit data frame format
|
||||||
|
// 4. Configure the LSBFIRST bit in the SPI_CR1 register to define the frame
|
||||||
|
// format. This step is not required when the TI mode is selected.
|
||||||
|
// 5. If the NSS pin is required in input mode, in hardware mode, connect the
|
||||||
|
// NSS pin to a high-level signal during the complete byte transmit
|
||||||
|
// sequence. In NSS software mode, set the SSM and SSI bits in the SPI_CR1
|
||||||
|
// register. If the NSS pin is required in output mode, the SSOE bit only
|
||||||
|
// should be set. This step is not required when the TI mode is selected.
|
||||||
|
// 6. Set the FRF bit in SPI_CR2 to select the TI protocol for serial
|
||||||
|
// communications.
|
||||||
|
// 7. The MSTR and SPE bits must be set (they remain set only if the NSS pin
|
||||||
|
// is connected to a high-level signal).
|
||||||
|
|
||||||
|
// disable SPI interface before any configuration changes
|
||||||
|
spi.Bus.CR1.ClearBits(stm32.SPI_CR1_SPE)
|
||||||
|
|
||||||
// enable clock for SPI
|
// enable clock for SPI
|
||||||
enableAltFuncClock(unsafe.Pointer(spi.Bus))
|
enableAltFuncClock(unsafe.Pointer(spi.Bus))
|
||||||
|
|
||||||
|
// init pins
|
||||||
|
if config.SCK == 0 && config.SDO == 0 && config.SDI == 0 {
|
||||||
|
config.SCK = SPI0_SCK_PIN
|
||||||
|
config.SDO = SPI0_SDO_PIN
|
||||||
|
config.SDI = SPI0_SDI_PIN
|
||||||
|
}
|
||||||
|
spi.configurePins(config)
|
||||||
|
|
||||||
// Get SPI baud rate based on the bus speed it's attached to
|
// Get SPI baud rate based on the bus speed it's attached to
|
||||||
var conf uint32 = spi.getBaudRate(config)
|
var conf uint32 = spi.getBaudRate(config)
|
||||||
|
|
||||||
|
@ -39,61 +66,72 @@ func (spi SPI) Configure(config SPIConfig) {
|
||||||
|
|
||||||
// set polarity and phase on the SPI interface
|
// set polarity and phase on the SPI interface
|
||||||
switch config.Mode {
|
switch config.Mode {
|
||||||
case Mode0:
|
|
||||||
conf &^= (1 << stm32.SPI_CR1_CPOL_Pos)
|
|
||||||
conf &^= (1 << stm32.SPI_CR1_CPHA_Pos)
|
|
||||||
case Mode1:
|
case Mode1:
|
||||||
conf &^= (1 << stm32.SPI_CR1_CPOL_Pos)
|
conf |= stm32.SPI_CR1_CPHA
|
||||||
conf |= (1 << stm32.SPI_CR1_CPHA_Pos)
|
|
||||||
case Mode2:
|
case Mode2:
|
||||||
conf |= (1 << stm32.SPI_CR1_CPOL_Pos)
|
conf |= stm32.SPI_CR1_CPOL
|
||||||
conf &^= (1 << stm32.SPI_CR1_CPHA_Pos)
|
|
||||||
case Mode3:
|
case Mode3:
|
||||||
conf |= (1 << stm32.SPI_CR1_CPOL_Pos)
|
conf |= stm32.SPI_CR1_CPOL
|
||||||
conf |= (1 << stm32.SPI_CR1_CPHA_Pos)
|
conf |= stm32.SPI_CR1_CPHA
|
||||||
default: // to mode 0
|
|
||||||
conf &^= (1 << stm32.SPI_CR1_CPOL_Pos)
|
|
||||||
conf &^= (1 << stm32.SPI_CR1_CPHA_Pos)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// set to SPI controller
|
// configure as SPI master
|
||||||
conf |= stm32.SPI_CR1_MSTR
|
conf |= stm32.SPI_CR1_MSTR | stm32.SPI_CR1_SSI
|
||||||
|
|
||||||
// disable MCU acting as SPI peripheral
|
// enable the SPI interface
|
||||||
conf |= stm32.SPI_CR1_SSM | stm32.SPI_CR1_SSI
|
conf |= stm32.SPI_CR1_SPE
|
||||||
|
|
||||||
|
// use software CS (GPIO) by default
|
||||||
|
conf |= stm32.SPI_CR1_SSM
|
||||||
|
|
||||||
// now set the configuration
|
// now set the configuration
|
||||||
spi.Bus.CR1.Set(conf)
|
spi.Bus.CR1.Set(conf)
|
||||||
|
spi.Bus.CR2.SetBits((conf & stm32.SPI_CR1_SSM_Msk) >> 16)
|
||||||
// init pins
|
|
||||||
if config.SCK == 0 && config.SDO == 0 && config.SDI == 0 {
|
|
||||||
config.SCK = SPI0_SCK_PIN
|
|
||||||
config.SDO = SPI0_SDO_PIN
|
|
||||||
config.SDI = SPI0_SDI_PIN
|
|
||||||
}
|
|
||||||
spi.configurePins(config)
|
|
||||||
|
|
||||||
// enable SPI interface
|
|
||||||
spi.Bus.CR1.SetBits(stm32.SPI_CR1_SPE)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Transfer writes/reads a single byte using the SPI interface.
|
// Transfer writes/reads a single byte using the SPI interface.
|
||||||
func (spi SPI) Transfer(w byte) (byte, error) {
|
func (spi SPI) Transfer(w byte) (byte, error) {
|
||||||
// Write data to be transmitted to the SPI data register
|
|
||||||
|
// 1. Enable the SPI by setting the SPE bit to 1.
|
||||||
|
// 2. Write the first data item to be transmitted into the SPI_DR register
|
||||||
|
// (this clears the TXE flag).
|
||||||
|
// 3. Wait until TXE=1 and write the second data item to be transmitted. Then
|
||||||
|
// wait until RXNE=1 and read the SPI_DR to get the first received data
|
||||||
|
// item (this clears the RXNE bit). Repeat this operation for each data
|
||||||
|
// item to be transmitted/received until the n–1 received data.
|
||||||
|
// 4. Wait until RXNE=1 and read the last received data.
|
||||||
|
// 5. Wait until TXE=1 and then wait until BSY=0 before disabling the SPI.
|
||||||
|
|
||||||
|
// put output word (8-bit) in data register (DR), which is parallel-loaded
|
||||||
|
// into shift register, and shifted out on MOSI.
|
||||||
spi.Bus.DR.Set(uint32(w))
|
spi.Bus.DR.Set(uint32(w))
|
||||||
|
|
||||||
// Wait until transmit complete
|
// wait for SPI bus receive buffer not empty bit (RXNE) to be set.
|
||||||
for !spi.Bus.SR.HasBits(stm32.SPI_SR_TXE) {
|
// warning: blocks forever until this condition is met.
|
||||||
}
|
|
||||||
|
|
||||||
// Wait until receive complete
|
|
||||||
for !spi.Bus.SR.HasBits(stm32.SPI_SR_RXNE) {
|
for !spi.Bus.SR.HasBits(stm32.SPI_SR_RXNE) {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Wait until SPI is not busy
|
// copy input word (8-bit) in data register (DR), which was shifted in on MISO
|
||||||
|
// and parallel-loaded into register.
|
||||||
|
data := byte(spi.Bus.DR.Get())
|
||||||
|
|
||||||
|
// wait for SPI bus transmit buffer empty bit (TXE) to be set.
|
||||||
|
// warning: blocks forever until this condition is met.
|
||||||
|
for !spi.Bus.SR.HasBits(stm32.SPI_SR_TXE) {
|
||||||
|
}
|
||||||
|
|
||||||
|
// wait for SPI bus busy bit (BSY) to be clear to indicate synchronous
|
||||||
|
// transfer complete. this will effectively prevent this Transfer() function
|
||||||
|
// from being capable of maintaining high-bandwidth communication throughput,
|
||||||
|
// but it will help guarantee stability on the bus.
|
||||||
for spi.Bus.SR.HasBits(stm32.SPI_SR_BSY) {
|
for spi.Bus.SR.HasBits(stm32.SPI_SR_BSY) {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// clear the overrun flag (only in full-duplex mode)
|
||||||
|
if !spi.Bus.CR1.HasBits(stm32.SPI_CR1_RXONLY | stm32.SPI_CR1_BIDIMODE | stm32.SPI_CR1_BIDIOE) {
|
||||||
|
spi.Bus.SR.Get()
|
||||||
|
}
|
||||||
|
|
||||||
// Return received data from SPI data register
|
// Return received data from SPI data register
|
||||||
return byte(spi.Bus.DR.Get()), nil
|
return data, nil
|
||||||
}
|
}
|
||||||
|
|
|
@ -6,6 +6,7 @@ package machine
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"device/stm32"
|
"device/stm32"
|
||||||
|
"math/bits"
|
||||||
"runtime/interrupt"
|
"runtime/interrupt"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -46,8 +47,47 @@ type SPI struct {
|
||||||
AltFuncSelector stm32.AltFunc
|
AltFuncSelector stm32.AltFunc
|
||||||
}
|
}
|
||||||
|
|
||||||
func (spi SPI) configurePins(config SPIConfig) {}
|
func (spi SPI) configurePins(config SPIConfig) {
|
||||||
func (spi SPI) getBaudRate(config SPIConfig) uint32 { return 0 }
|
config.SCK.ConfigureAltFunc(PinConfig{Mode: PinModeSPICLK}, spi.AltFuncSelector)
|
||||||
|
config.SDO.ConfigureAltFunc(PinConfig{Mode: PinModeSPISDO}, spi.AltFuncSelector)
|
||||||
|
config.SDI.ConfigureAltFunc(PinConfig{Mode: PinModeSPISDI}, spi.AltFuncSelector)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (spi SPI) getBaudRate(config SPIConfig) uint32 {
|
||||||
|
var clock uint32
|
||||||
|
switch spi.Bus {
|
||||||
|
case stm32.SPI1:
|
||||||
|
clock = CPUFrequency() / 2
|
||||||
|
case stm32.SPI2, stm32.SPI3:
|
||||||
|
clock = CPUFrequency() / 4
|
||||||
|
}
|
||||||
|
|
||||||
|
// limit requested frequency to bus frequency and min frequency (DIV256)
|
||||||
|
freq := config.Frequency
|
||||||
|
if min := clock / 256; freq < min {
|
||||||
|
freq = min
|
||||||
|
} else if freq > clock {
|
||||||
|
freq = clock
|
||||||
|
}
|
||||||
|
|
||||||
|
// calculate the exact clock divisor (freq=clock/div -> div=clock/freq).
|
||||||
|
// truncation is fine, since it produces a less-than-or-equal divisor, and
|
||||||
|
// thus a greater-than-or-equal frequency.
|
||||||
|
// divisors only come in consecutive powers of 2, so we can use log2 (or,
|
||||||
|
// equivalently, bits.Len - 1) to convert to respective enum value.
|
||||||
|
div := bits.Len32(clock/freq) - 1
|
||||||
|
|
||||||
|
// but DIV1 (2^0) is not permitted, as the least divisor is DIV2 (2^1), so
|
||||||
|
// subtract 1 from the log2 value, keeping a lower bound of 0
|
||||||
|
if div < 0 {
|
||||||
|
div = 0
|
||||||
|
} else if div > 0 {
|
||||||
|
div--
|
||||||
|
}
|
||||||
|
|
||||||
|
// finally, shift the enumerated value into position for SPI CR1
|
||||||
|
return uint32(div) << stm32.SPI_CR1_BR_Pos
|
||||||
|
}
|
||||||
|
|
||||||
// -- I2C ----------------------------------------------------------------------
|
// -- I2C ----------------------------------------------------------------------
|
||||||
|
|
||||||
|
|
Загрузка…
Создание таблицы
Сослаться в новой задаче