aboutsummaryrefslogtreecommitdiffhomepage
path: root/src/machine/machine_stm32_spi.go
blob: 72d43166165eda761c14f73bb93e07d88818bdfc (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
//go:build stm32 && !stm32f7x2 && !stm32l5x2

package machine

// Peripheral abstraction layer for SPI on the stm32 family

import (
	"device/stm32"
	"runtime/volatile"
	"unsafe"
)

// SPIConfig is used to store config info for SPI.
type SPIConfig struct {
	Frequency uint32
	SCK       Pin
	SDO       Pin
	SDI       Pin
	LSBFirst  bool
	Mode      uint8
}

// Configure is intended to setup the STM32 SPI1 interface.
func (spi SPI) Configure(config SPIConfig) error {

	// -- 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
	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
	var conf uint32 = spi.getBaudRate(config)

	// set bit transfer order
	if config.LSBFirst {
		conf |= stm32.SPI_CR1_LSBFIRST
	}

	// set polarity and phase on the SPI interface
	switch config.Mode {
	case Mode1:
		conf |= stm32.SPI_CR1_CPHA
	case Mode2:
		conf |= stm32.SPI_CR1_CPOL
	case Mode3:
		conf |= stm32.SPI_CR1_CPOL
		conf |= stm32.SPI_CR1_CPHA
	}

	// configure as SPI master
	conf |= stm32.SPI_CR1_MSTR | stm32.SPI_CR1_SSI

	// enable the SPI interface
	conf |= stm32.SPI_CR1_SPE

	// use software CS (GPIO) by default
	conf |= stm32.SPI_CR1_SSM

	// now set the configuration
	spi.Bus.CR1.Set(conf)

	// Series-specific configuration to set 8-bit transfer mode
	spi.config8Bits()

	// enable SPI
	spi.Bus.CR1.SetBits(stm32.SPI_CR1_SPE)

	return nil
}

// Transfer writes/reads a single byte using the SPI interface.
func (spi SPI) Transfer(w byte) (byte, error) {

	// 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.  Some series have 16-bit
	// register but writes must be strictly 8-bit to output a byte.  Writing
	// 16-bits indicates a packed transfer (2 bytes).
	(*volatile.Register8)(unsafe.Pointer(&spi.Bus.DR.Reg)).Set(w)

	// wait for SPI bus receive buffer not empty bit (RXNE) to be set.
	// warning: blocks forever until this condition is met.
	for !spi.Bus.SR.HasBits(stm32.SPI_SR_RXNE) {
	}

	// 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) {
	}

	// 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 data, nil
}