Skip to content

Commit 9ad2315

Browse files
authored
feather-stm32f405: add SPI support (#1377)
* machine/stm32f405: add SPI support
1 parent 431e51b commit 9ad2315

File tree

3 files changed

+135
-41
lines changed

3 files changed

+135
-41
lines changed

src/machine/board_feather-stm32f405.go

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -178,6 +178,22 @@ const (
178178
SPI_SDO_PIN = SPI0_SDO_PIN //
179179
)
180180

181+
var (
182+
SPI1 = SPI{
183+
Bus: stm32.SPI2,
184+
AltFuncSelector: stm32.AF5_SPI1_SPI2,
185+
}
186+
SPI2 = SPI{
187+
Bus: stm32.SPI3,
188+
AltFuncSelector: stm32.AF6_SPI3,
189+
}
190+
SPI3 = SPI{
191+
Bus: stm32.SPI1,
192+
AltFuncSelector: stm32.AF5_SPI1_SPI2,
193+
}
194+
SPI0 = SPI1
195+
)
196+
181197
func initSPI() {}
182198

183199
// -- I2C ----------------------------------------------------------------------

src/machine/machine_stm32_spi.go

Lines changed: 77 additions & 39 deletions
Original file line numberDiff line numberDiff line change
@@ -20,15 +20,42 @@ type SPIConfig struct {
2020
}
2121

2222
// Configure is intended to setup the STM32 SPI1 interface.
23-
// Features still TODO:
24-
// - support SPI2 and SPI3
25-
// - allow setting data size to 16 bits?
26-
// - allow setting direction in HW for additional optimization?
27-
// - hardware SS pin?
2823
func (spi SPI) Configure(config SPIConfig) {
24+
25+
// -- CONFIGURING THE SPI IN MASTER MODE --
26+
//
27+
// 1. Select the BR[2:0] bits to define the serial clock baud rate (see
28+
// SPI_CR1 register).
29+
// 2. Select the CPOL and CPHA bits to define one of the four relationships
30+
// between the data transfer and the serial clock (see Figure 248). This
31+
// step is not required when the TI mode is selected.
32+
// 3. Set the DFF bit to define 8- or 16-bit data frame format
33+
// 4. Configure the LSBFIRST bit in the SPI_CR1 register to define the frame
34+
// format. This step is not required when the TI mode is selected.
35+
// 5. If the NSS pin is required in input mode, in hardware mode, connect the
36+
// NSS pin to a high-level signal during the complete byte transmit
37+
// sequence. In NSS software mode, set the SSM and SSI bits in the SPI_CR1
38+
// register. If the NSS pin is required in output mode, the SSOE bit only
39+
// should be set. This step is not required when the TI mode is selected.
40+
// 6. Set the FRF bit in SPI_CR2 to select the TI protocol for serial
41+
// communications.
42+
// 7. The MSTR and SPE bits must be set (they remain set only if the NSS pin
43+
// is connected to a high-level signal).
44+
45+
// disable SPI interface before any configuration changes
46+
spi.Bus.CR1.ClearBits(stm32.SPI_CR1_SPE)
47+
2948
// enable clock for SPI
3049
enableAltFuncClock(unsafe.Pointer(spi.Bus))
3150

51+
// init pins
52+
if config.SCK == 0 && config.SDO == 0 && config.SDI == 0 {
53+
config.SCK = SPI0_SCK_PIN
54+
config.SDO = SPI0_SDO_PIN
55+
config.SDI = SPI0_SDI_PIN
56+
}
57+
spi.configurePins(config)
58+
3259
// Get SPI baud rate based on the bus speed it's attached to
3360
var conf uint32 = spi.getBaudRate(config)
3461

@@ -39,61 +66,72 @@ func (spi SPI) Configure(config SPIConfig) {
3966

4067
// set polarity and phase on the SPI interface
4168
switch config.Mode {
42-
case Mode0:
43-
conf &^= (1 << stm32.SPI_CR1_CPOL_Pos)
44-
conf &^= (1 << stm32.SPI_CR1_CPHA_Pos)
4569
case Mode1:
46-
conf &^= (1 << stm32.SPI_CR1_CPOL_Pos)
47-
conf |= (1 << stm32.SPI_CR1_CPHA_Pos)
70+
conf |= stm32.SPI_CR1_CPHA
4871
case Mode2:
49-
conf |= (1 << stm32.SPI_CR1_CPOL_Pos)
50-
conf &^= (1 << stm32.SPI_CR1_CPHA_Pos)
72+
conf |= stm32.SPI_CR1_CPOL
5173
case Mode3:
52-
conf |= (1 << stm32.SPI_CR1_CPOL_Pos)
53-
conf |= (1 << stm32.SPI_CR1_CPHA_Pos)
54-
default: // to mode 0
55-
conf &^= (1 << stm32.SPI_CR1_CPOL_Pos)
56-
conf &^= (1 << stm32.SPI_CR1_CPHA_Pos)
74+
conf |= stm32.SPI_CR1_CPOL
75+
conf |= stm32.SPI_CR1_CPHA
5776
}
5877

59-
// set to SPI controller
60-
conf |= stm32.SPI_CR1_MSTR
78+
// configure as SPI master
79+
conf |= stm32.SPI_CR1_MSTR | stm32.SPI_CR1_SSI
80+
81+
// enable the SPI interface
82+
conf |= stm32.SPI_CR1_SPE
6183

62-
// disable MCU acting as SPI peripheral
63-
conf |= stm32.SPI_CR1_SSM | stm32.SPI_CR1_SSI
84+
// use software CS (GPIO) by default
85+
conf |= stm32.SPI_CR1_SSM
6486

6587
// now set the configuration
6688
spi.Bus.CR1.Set(conf)
67-
68-
// init pins
69-
if config.SCK == 0 && config.SDO == 0 && config.SDI == 0 {
70-
config.SCK = SPI0_SCK_PIN
71-
config.SDO = SPI0_SDO_PIN
72-
config.SDI = SPI0_SDI_PIN
73-
}
74-
spi.configurePins(config)
75-
76-
// enable SPI interface
77-
spi.Bus.CR1.SetBits(stm32.SPI_CR1_SPE)
89+
spi.Bus.CR2.SetBits((conf & stm32.SPI_CR1_SSM_Msk) >> 16)
7890
}
7991

8092
// Transfer writes/reads a single byte using the SPI interface.
8193
func (spi SPI) Transfer(w byte) (byte, error) {
82-
// Write data to be transmitted to the SPI data register
94+
95+
// 1. Enable the SPI by setting the SPE bit to 1.
96+
// 2. Write the first data item to be transmitted into the SPI_DR register
97+
// (this clears the TXE flag).
98+
// 3. Wait until TXE=1 and write the second data item to be transmitted. Then
99+
// wait until RXNE=1 and read the SPI_DR to get the first received data
100+
// item (this clears the RXNE bit). Repeat this operation for each data
101+
// item to be transmitted/received until the n–1 received data.
102+
// 4. Wait until RXNE=1 and read the last received data.
103+
// 5. Wait until TXE=1 and then wait until BSY=0 before disabling the SPI.
104+
105+
// put output word (8-bit) in data register (DR), which is parallel-loaded
106+
// into shift register, and shifted out on MOSI.
83107
spi.Bus.DR.Set(uint32(w))
84108

85-
// Wait until transmit complete
86-
for !spi.Bus.SR.HasBits(stm32.SPI_SR_TXE) {
109+
// wait for SPI bus receive buffer not empty bit (RXNE) to be set.
110+
// warning: blocks forever until this condition is met.
111+
for !spi.Bus.SR.HasBits(stm32.SPI_SR_RXNE) {
87112
}
88113

89-
// Wait until receive complete
90-
for !spi.Bus.SR.HasBits(stm32.SPI_SR_RXNE) {
114+
// copy input word (8-bit) in data register (DR), which was shifted in on MISO
115+
// and parallel-loaded into register.
116+
data := byte(spi.Bus.DR.Get())
117+
118+
// wait for SPI bus transmit buffer empty bit (TXE) to be set.
119+
// warning: blocks forever until this condition is met.
120+
for !spi.Bus.SR.HasBits(stm32.SPI_SR_TXE) {
91121
}
92122

93-
// Wait until SPI is not busy
123+
// wait for SPI bus busy bit (BSY) to be clear to indicate synchronous
124+
// transfer complete. this will effectively prevent this Transfer() function
125+
// from being capable of maintaining high-bandwidth communication throughput,
126+
// but it will help guarantee stability on the bus.
94127
for spi.Bus.SR.HasBits(stm32.SPI_SR_BSY) {
95128
}
96129

130+
// clear the overrun flag (only in full-duplex mode)
131+
if !spi.Bus.CR1.HasBits(stm32.SPI_CR1_RXONLY | stm32.SPI_CR1_BIDIMODE | stm32.SPI_CR1_BIDIOE) {
132+
spi.Bus.SR.Get()
133+
}
134+
97135
// Return received data from SPI data register
98-
return byte(spi.Bus.DR.Get()), nil
136+
return data, nil
99137
}

src/machine/machine_stm32f405.go

Lines changed: 42 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ package machine
66

77
import (
88
"device/stm32"
9+
"math/bits"
910
"runtime/interrupt"
1011
)
1112

@@ -46,8 +47,47 @@ type SPI struct {
4647
AltFuncSelector stm32.AltFunc
4748
}
4849

49-
func (spi SPI) configurePins(config SPIConfig) {}
50-
func (spi SPI) getBaudRate(config SPIConfig) uint32 { return 0 }
50+
func (spi SPI) configurePins(config SPIConfig) {
51+
config.SCK.ConfigureAltFunc(PinConfig{Mode: PinModeSPICLK}, spi.AltFuncSelector)
52+
config.SDO.ConfigureAltFunc(PinConfig{Mode: PinModeSPISDO}, spi.AltFuncSelector)
53+
config.SDI.ConfigureAltFunc(PinConfig{Mode: PinModeSPISDI}, spi.AltFuncSelector)
54+
}
55+
56+
func (spi SPI) getBaudRate(config SPIConfig) uint32 {
57+
var clock uint32
58+
switch spi.Bus {
59+
case stm32.SPI1:
60+
clock = CPUFrequency() / 2
61+
case stm32.SPI2, stm32.SPI3:
62+
clock = CPUFrequency() / 4
63+
}
64+
65+
// limit requested frequency to bus frequency and min frequency (DIV256)
66+
freq := config.Frequency
67+
if min := clock / 256; freq < min {
68+
freq = min
69+
} else if freq > clock {
70+
freq = clock
71+
}
72+
73+
// calculate the exact clock divisor (freq=clock/div -> div=clock/freq).
74+
// truncation is fine, since it produces a less-than-or-equal divisor, and
75+
// thus a greater-than-or-equal frequency.
76+
// divisors only come in consecutive powers of 2, so we can use log2 (or,
77+
// equivalently, bits.Len - 1) to convert to respective enum value.
78+
div := bits.Len32(clock/freq) - 1
79+
80+
// but DIV1 (2^0) is not permitted, as the least divisor is DIV2 (2^1), so
81+
// subtract 1 from the log2 value, keeping a lower bound of 0
82+
if div < 0 {
83+
div = 0
84+
} else if div > 0 {
85+
div--
86+
}
87+
88+
// finally, shift the enumerated value into position for SPI CR1
89+
return uint32(div) << stm32.SPI_CR1_BR_Pos
90+
}
5191

5292
// -- I2C ----------------------------------------------------------------------
5393

0 commit comments

Comments
 (0)