github.com/tinygo-org/tinygo@v0.31.3-0.20240404173401-90b0bf646c27/src/machine/machine_esp32c3_spi.go (about)

     1  //go:build esp32c3
     2  
     3  package machine
     4  
     5  // On the C3 variant, SPI2 is a general purpose SPI controller. SPI0 and SPI1
     6  // are used internally to access the ESP32-C3’s attached flash memory. Due to
     7  // different registers between SPI2 and the other SPI ports, this driver
     8  // currently supports only the the general purpose FSPI SPI2 controller.
     9  // https://docs.espressif.com/projects/esp-idf/en/latest/esp32c3/api-reference/peripherals/spi_master.html
    10  
    11  import (
    12  	"device/esp"
    13  	"errors"
    14  	"runtime/volatile"
    15  	"unsafe"
    16  )
    17  
    18  const (
    19  	SPI_MODE0 = uint8(0)
    20  	SPI_MODE1 = uint8(1)
    21  	SPI_MODE2 = uint8(2)
    22  	SPI_MODE3 = uint8(3)
    23  
    24  	FSPICLK_IN_IDX  = uint32(63)
    25  	FSPICLK_OUT_IDX = uint32(63)
    26  	FSPIQ_IN_IDX    = uint32(64)
    27  	FSPIQ_OUT_IDX   = uint32(64)
    28  	FSPID_IN_IDX    = uint32(65)
    29  	FSPID_OUT_IDX   = uint32(65)
    30  	FSPIHD_IN_IDX   = uint32(66)
    31  	FSPIHD_OUT_IDX  = uint32(66)
    32  	FSPIWP_IN_IDX   = uint32(67)
    33  	FSPIWP_OUT_IDX  = uint32(67)
    34  	FSPICS0_IN_IDX  = uint32(68)
    35  	FSPICS0_OUT_IDX = uint32(68)
    36  	FSPICS1_OUT_IDX = uint32(69)
    37  	FSPICS2_OUT_IDX = uint32(70)
    38  	FSPICS3_OUT_IDX = uint32(71)
    39  	FSPICS4_OUT_IDX = uint32(72)
    40  	FSPICS5_OUT_IDX = uint32(73)
    41  )
    42  
    43  var (
    44  	ErrInvalidSPIBus  = errors.New("machine: SPI bus is invalid")
    45  	ErrInvalidSPIMode = errors.New("machine: SPI mode is invalid")
    46  )
    47  
    48  // Serial Peripheral Interface on the ESP32-C3.
    49  type SPI struct {
    50  	Bus *esp.SPI2_Type
    51  }
    52  
    53  var (
    54  	// SPI0 and SPI1 are reserved for use by the caching system etc.
    55  	SPI2 = SPI{esp.SPI2}
    56  )
    57  
    58  // SPIConfig is used to store config info for SPI.
    59  type SPIConfig struct {
    60  	Frequency uint32
    61  	SCK       Pin   // Serial Clock
    62  	SDO       Pin   // Serial Data Out (MOSI)
    63  	SDI       Pin   // Serial Data In  (MISO)
    64  	CS        Pin   // Chip Select (optional)
    65  	LSBFirst  bool  // MSB is default
    66  	Mode      uint8 // SPI_MODE0 is default
    67  }
    68  
    69  // Compute the SPI bus frequency from the CPU frequency.
    70  func freqToClockDiv(hz uint32) uint32 {
    71  	fcpu := CPUFrequency()
    72  	if hz >= fcpu { // maximum frequency
    73  		return 1 << 31
    74  	}
    75  	if hz < (fcpu / (16 * 64)) { // minimum frequency
    76  		return 15<<18 | 63<<12 | 31<<6 | 63 // pre=15, n=63
    77  	}
    78  
    79  	// iterate looking for an exact match
    80  	// or iterate all 16 prescaler options
    81  	// looking for the smallest error
    82  	var bestPre, bestN, bestErr uint32
    83  	bestN = 1
    84  	bestErr = 0xffffffff
    85  	q := uint32(float32(pplClockFreq)/float32(hz) + float32(0.5))
    86  	for p := uint32(0); p < 16; p++ {
    87  		n := q/(p+1) - 1
    88  		if n < 1 { // prescaler became too large, stop enum
    89  			break
    90  		}
    91  		if n > 63 { // prescaler too small, skip to next
    92  			continue
    93  		}
    94  
    95  		freq := fcpu / ((p + 1) * (n + 1))
    96  		if freq == hz { // exact match
    97  			return p<<18 | n<<12 | (n/2)<<6 | n
    98  		}
    99  
   100  		var err uint32
   101  		if freq < hz {
   102  			err = hz - freq
   103  		} else {
   104  			err = freq - hz
   105  		}
   106  		if err < bestErr {
   107  			bestErr = err
   108  			bestPre = p
   109  			bestN = n
   110  		}
   111  	}
   112  
   113  	return bestPre<<18 | bestN<<12 | (bestN/2)<<6 | bestN
   114  }
   115  
   116  // Configure and make the SPI peripheral ready to use.
   117  func (spi SPI) Configure(config SPIConfig) error {
   118  	// right now this is only setup to work for the esp32c3 spi2 bus
   119  	if spi.Bus != esp.SPI2 {
   120  		return ErrInvalidSPIBus
   121  	}
   122  
   123  	// periph module reset
   124  	esp.SYSTEM.SetPERIP_RST_EN0_SPI2_RST(1)
   125  	esp.SYSTEM.SetPERIP_RST_EN0_SPI2_RST(0)
   126  
   127  	// periph module enable
   128  	esp.SYSTEM.SetPERIP_CLK_EN0_SPI2_CLK_EN(1)
   129  	esp.SYSTEM.SetPERIP_RST_EN0_SPI2_RST(0)
   130  
   131  	// init the spi2 bus
   132  	spi.Bus.SLAVE.Set(0)
   133  	spi.Bus.MISC.Set(0)
   134  	spi.Bus.USER.Set(0)
   135  	spi.Bus.USER1.Set(0)
   136  	spi.Bus.CTRL.Set(0)
   137  	spi.Bus.CLK_GATE.Set(0)
   138  	spi.Bus.DMA_CONF.Set(0)
   139  	spi.Bus.SetDMA_CONF_RX_AFIFO_RST(1)
   140  	spi.Bus.SetDMA_CONF_BUF_AFIFO_RST(1)
   141  	spi.Bus.CLOCK.Set(0)
   142  
   143  	// clear data buf
   144  	spi.Bus.SetW0(0)
   145  	spi.Bus.SetW1(0)
   146  	spi.Bus.SetW2(0)
   147  	spi.Bus.SetW3(0)
   148  	spi.Bus.SetW4(0)
   149  	spi.Bus.SetW5(0)
   150  	spi.Bus.SetW6(0)
   151  	spi.Bus.SetW7(0)
   152  	spi.Bus.SetW8(0)
   153  	spi.Bus.SetW9(0)
   154  	spi.Bus.SetW10(0)
   155  	spi.Bus.SetW11(0)
   156  	spi.Bus.SetW12(0)
   157  	spi.Bus.SetW13(0)
   158  	spi.Bus.SetW14(0)
   159  	spi.Bus.SetW15(0)
   160  
   161  	// start the spi2 bus
   162  	spi.Bus.SetCLK_GATE_CLK_EN(1)
   163  	spi.Bus.SetCLK_GATE_MST_CLK_SEL(1)
   164  	spi.Bus.SetCLK_GATE_MST_CLK_ACTIVE(1)
   165  	spi.Bus.SetDMA_CONF_SLV_TX_SEG_TRANS_CLR_EN(1)
   166  	spi.Bus.SetDMA_CONF_SLV_RX_SEG_TRANS_CLR_EN(1)
   167  	spi.Bus.SetDMA_CONF_DMA_SLV_SEG_TRANS_EN(0)
   168  	spi.Bus.SetUSER_USR_MOSI(1)
   169  	spi.Bus.SetUSER_USR_MISO(1)
   170  	spi.Bus.SetUSER_DOUTDIN(1)
   171  
   172  	// set spi2 data mode
   173  	switch config.Mode {
   174  	case SPI_MODE0:
   175  		spi.Bus.SetMISC_CK_IDLE_EDGE(0)
   176  		spi.Bus.SetUSER_CK_OUT_EDGE(0)
   177  	case SPI_MODE1:
   178  		spi.Bus.SetMISC_CK_IDLE_EDGE(0)
   179  		spi.Bus.SetUSER_CK_OUT_EDGE(1)
   180  	case SPI_MODE2:
   181  		spi.Bus.SetMISC_CK_IDLE_EDGE(1)
   182  		spi.Bus.SetUSER_CK_OUT_EDGE(1)
   183  	case SPI_MODE3:
   184  		spi.Bus.SetMISC_CK_IDLE_EDGE(1)
   185  		spi.Bus.SetUSER_CK_OUT_EDGE(0)
   186  	default:
   187  		return ErrInvalidSPIMode
   188  	}
   189  
   190  	// set spi2 bit order
   191  	if config.LSBFirst {
   192  		spi.Bus.SetCTRL_WR_BIT_ORDER(1) // LSB first
   193  		spi.Bus.SetCTRL_RD_BIT_ORDER(1)
   194  	} else {
   195  		spi.Bus.SetCTRL_WR_BIT_ORDER(0) // MSB first
   196  		spi.Bus.SetCTRL_RD_BIT_ORDER(0)
   197  	}
   198  
   199  	// configure SPI bus clock
   200  	spi.Bus.CLOCK.Set(freqToClockDiv(config.Frequency))
   201  
   202  	// configure esp32c3 gpio pin matrix
   203  	config.SDI.Configure(PinConfig{Mode: PinInput})
   204  	inFunc(FSPIQ_IN_IDX).Set(esp.GPIO_FUNC_IN_SEL_CFG_SEL | uint32(config.SDI))
   205  	config.SDO.Configure(PinConfig{Mode: PinOutput})
   206  	config.SDO.outFunc().Set(FSPID_OUT_IDX)
   207  	config.SCK.Configure(PinConfig{Mode: PinOutput})
   208  	config.SCK.outFunc().Set(FSPICLK_OUT_IDX)
   209  	if config.CS != NoPin {
   210  		config.CS.Configure(PinConfig{Mode: PinOutput})
   211  		config.CS.outFunc().Set(FSPICS0_OUT_IDX)
   212  	}
   213  
   214  	return nil
   215  }
   216  
   217  // Transfer writes/reads a single byte using the SPI interface. If you need to
   218  // transfer larger amounts of data, Tx will be faster.
   219  func (spi SPI) Transfer(w byte) (byte, error) {
   220  	spi.Bus.SetMS_DLEN_MS_DATA_BITLEN(7)
   221  
   222  	spi.Bus.SetW0(uint32(w))
   223  
   224  	// Send/receive byte.
   225  	spi.Bus.SetCMD_UPDATE(1)
   226  	for spi.Bus.GetCMD_UPDATE() != 0 {
   227  	}
   228  
   229  	spi.Bus.SetCMD_USR(1)
   230  	for spi.Bus.GetCMD_USR() != 0 {
   231  	}
   232  
   233  	// The received byte is stored in W0.
   234  	return byte(spi.Bus.GetW0()), nil
   235  }
   236  
   237  // Tx handles read/write operation for SPI interface. Since SPI is a syncronous write/read
   238  // interface, there must always be the same number of bytes written as bytes read.
   239  // This is accomplished by sending zero bits if r is bigger than w or discarding
   240  // the incoming data if w is bigger than r.
   241  func (spi SPI) Tx(w, r []byte) error {
   242  	toTransfer := len(w)
   243  	if len(r) > toTransfer {
   244  		toTransfer = len(r)
   245  	}
   246  
   247  	for toTransfer > 0 {
   248  		// Chunk 64 bytes at a time.
   249  		chunkSize := toTransfer
   250  		if chunkSize > 64 {
   251  			chunkSize = 64
   252  		}
   253  
   254  		// Fill tx buffer.
   255  		transferWords := (*[16]volatile.Register32)(unsafe.Pointer(uintptr(unsafe.Pointer(&spi.Bus.W0))))
   256  		if len(w) >= 64 {
   257  			// We can fill the entire 64-byte transfer buffer with data.
   258  			// This loop is slightly faster than the loop below.
   259  			for i := 0; i < 16; i++ {
   260  				word := uint32(w[i*4]) | uint32(w[i*4+1])<<8 | uint32(w[i*4+2])<<16 | uint32(w[i*4+3])<<24
   261  				transferWords[i].Set(word)
   262  			}
   263  		} else {
   264  			// We can't fill the entire transfer buffer, so we need to be a bit
   265  			// more careful.
   266  			// Note that parts of the transfer buffer that aren't used still
   267  			// need to be set to zero, otherwise we might be transferring
   268  			// garbage from a previous transmission if w is smaller than r.
   269  			for i := 0; i < 16; i++ {
   270  				var word uint32
   271  				if i*4+3 < len(w) {
   272  					word |= uint32(w[i*4+3]) << 24
   273  				}
   274  				if i*4+2 < len(w) {
   275  					word |= uint32(w[i*4+2]) << 16
   276  				}
   277  				if i*4+1 < len(w) {
   278  					word |= uint32(w[i*4+1]) << 8
   279  				}
   280  				if i*4+0 < len(w) {
   281  					word |= uint32(w[i*4+0]) << 0
   282  				}
   283  				transferWords[i].Set(word)
   284  			}
   285  		}
   286  
   287  		// Do the transfer.
   288  		spi.Bus.SetMS_DLEN_MS_DATA_BITLEN(uint32(chunkSize)*8 - 1)
   289  
   290  		spi.Bus.SetCMD_UPDATE(1)
   291  		for spi.Bus.GetCMD_UPDATE() != 0 {
   292  		}
   293  
   294  		spi.Bus.SetCMD_USR(1)
   295  		for spi.Bus.GetCMD_USR() != 0 {
   296  		}
   297  
   298  		// Read rx buffer.
   299  		rxSize := 64
   300  		if rxSize > len(r) {
   301  			rxSize = len(r)
   302  		}
   303  		for i := 0; i < rxSize; i++ {
   304  			r[i] = byte(transferWords[i/4].Get() >> ((i % 4) * 8))
   305  		}
   306  
   307  		// Cut off some part of the output buffer so the next iteration we will
   308  		// only send the remaining bytes.
   309  		if len(w) < chunkSize {
   310  			w = nil
   311  		} else {
   312  			w = w[chunkSize:]
   313  		}
   314  		if len(r) < chunkSize {
   315  			r = nil
   316  		} else {
   317  			r = r[chunkSize:]
   318  		}
   319  		toTransfer -= chunkSize
   320  	}
   321  
   322  	return nil
   323  }