mirror of
https://github.com/tinygo-org/tinygo.git
synced 2026-07-26 14:48:40 +00:00
Compare commits
3 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 65a059a973 | |||
| cb95259adf | |||
| 24d718fa05 |
@@ -683,6 +683,56 @@ func (p Pin) getPinGrouping() (uint8, uint8) {
|
||||
return group, pin_in_group
|
||||
}
|
||||
|
||||
// Static DMA channel allocation.
|
||||
// If there are a lot of DMA using peripherals, we might need to switch to
|
||||
// dynamic allocation instead.
|
||||
const (
|
||||
dmaChannelSERCOM0 = iota
|
||||
dmaChannelSERCOM1
|
||||
dmaChannelSERCOM2
|
||||
dmaChannelSERCOM3
|
||||
dmaChannelSERCOM4
|
||||
dmaChannelSERCOM5
|
||||
dmaChannelSERCOM6
|
||||
dmaChannelSERCOM7
|
||||
dmaNumChannels
|
||||
)
|
||||
|
||||
// DMA descriptor structure. This structure is defined by the hardware, and is
|
||||
// described by "22.9 Register Summary - SRAM" in the datasheet.
|
||||
type dmaDescriptor struct {
|
||||
btctrl uint16
|
||||
btcnt uint16
|
||||
srcaddr unsafe.Pointer
|
||||
dstaddr unsafe.Pointer
|
||||
descaddr unsafe.Pointer
|
||||
}
|
||||
|
||||
//go:align 16
|
||||
var dmaDescriptorSection [dmaNumChannels]dmaDescriptor
|
||||
|
||||
//go:align 16
|
||||
var dmaDescriptorWritebackSection [dmaNumChannels]dmaDescriptor
|
||||
|
||||
// Enable and configure the DMAC peripheral if it hasn't been enabled already.
|
||||
func enableDMAC() {
|
||||
if !sam.DMAC.CTRL.HasBits(sam.DMAC_CTRL_DMAENABLE) {
|
||||
// Init DMAC.
|
||||
// First configure the clocks, then configure the DMA descriptors. Those
|
||||
// descriptors must live in SRAM and must be aligned on a 16-byte
|
||||
// boundary.
|
||||
// Some examples:
|
||||
// http://www.lucadavidian.com/2018/03/08/wifi-controlled-neo-pixels-strips/
|
||||
// https://svn.larosterna.com/oss/trunk/arduino/zerotimer/zerodma.cpp
|
||||
sam.MCLK.AHBMASK.SetBits(sam.MCLK_AHBMASK_DMAC_)
|
||||
sam.DMAC.BASEADDR.Set(uint32(uintptr(unsafe.Pointer(&dmaDescriptorSection))))
|
||||
sam.DMAC.WRBADDR.Set(uint32(uintptr(unsafe.Pointer(&dmaDescriptorWritebackSection))))
|
||||
|
||||
// Enable peripheral with all priorities.
|
||||
sam.DMAC.CTRL.SetBits(sam.DMAC_CTRL_DMAENABLE | sam.DMAC_CTRL_LVLEN0 | sam.DMAC_CTRL_LVLEN1 | sam.DMAC_CTRL_LVLEN2 | sam.DMAC_CTRL_LVLEN3)
|
||||
}
|
||||
}
|
||||
|
||||
// InitADC initializes the ADC.
|
||||
func InitADC() {
|
||||
// ADC Bias Calibration
|
||||
@@ -1652,6 +1702,101 @@ func (spi SPI) txrx(tx, rx []byte) {
|
||||
rx[len(rx)-1] = byte(spi.Bus.DATA.Get())
|
||||
}
|
||||
|
||||
// Channel to be used for SPI transfers.
|
||||
// These channels are currently statically allocated.
|
||||
func (spi SPI) dmaTxChannel() uint8 {
|
||||
return dmaChannelSERCOM0 + spi.SERCOM
|
||||
}
|
||||
|
||||
// IsAsync returns whether the SPI supports async operation (usually DMA).
|
||||
//
|
||||
// It returns true on the SAM D5x chips.
|
||||
func (spi SPI) IsAsync() bool {
|
||||
return true
|
||||
}
|
||||
|
||||
// Start a transfer in the background.
|
||||
//
|
||||
// After this, another StartTx() or Wait() must be called. The provided byte
|
||||
// slices (tx and rx) may only be accessed again after Wait() was called.
|
||||
func (s SPI) StartTx(tx, rx []byte) error {
|
||||
// Check whether we support doing this transfer using DMA.
|
||||
if len(rx) != 0 {
|
||||
return s.Tx(tx, rx)
|
||||
}
|
||||
if len(tx) == 0 {
|
||||
return nil // nothing to send/receive
|
||||
}
|
||||
if len(tx) != int(uint16(len(tx))) {
|
||||
// The transfer size is a 16-bit field.
|
||||
// TODO: chain multiple transfers in some way when encountering these
|
||||
// large buffer sizes. They're not currently implemented because
|
||||
// transferring 64kB of data is less commonly done.
|
||||
return s.Tx(tx, rx)
|
||||
}
|
||||
|
||||
// Enable DMAC (if not already enabled).
|
||||
enableDMAC()
|
||||
|
||||
// Wait until a possible previous transfer has been completed.
|
||||
s.Wait()
|
||||
|
||||
// Configure the DMA channel, if it hasn't been configured already.
|
||||
dstaddr := unsafe.Pointer(&s.Bus.DATA.Reg)
|
||||
if dmaDescriptorSection[s.dmaTxChannel()].dstaddr != dstaddr {
|
||||
// Configure channel descriptor.
|
||||
dmaDescriptorSection[s.dmaTxChannel()] = dmaDescriptor{
|
||||
btctrl: (1 << 0) | // VALID: Descriptor Valid
|
||||
(0 << 3) | // BLOCKACT=NOACT: Block Action
|
||||
(1 << 10) | // SRCINC: Source Address Increment Enable
|
||||
(0 << 11) | // DSTINC: Destination Address Increment Enable
|
||||
(1 << 12) | // STEPSEL=SRC: Step Selection
|
||||
(0 << 13), // STEPSIZE=X1: Address Increment Step Size
|
||||
dstaddr: dstaddr,
|
||||
}
|
||||
|
||||
// Reset channel.
|
||||
sam.DMAC.CHANNEL[s.dmaTxChannel()].CHCTRLA.ClearBits(sam.DMAC_CHANNEL_CHCTRLA_ENABLE)
|
||||
sam.DMAC.CHANNEL[s.dmaTxChannel()].CHCTRLA.SetBits(sam.DMAC_CHANNEL_CHCTRLA_SWRST)
|
||||
|
||||
// Configure channel.
|
||||
sam.DMAC.CHANNEL[s.dmaTxChannel()].CHPRILVL.Set(0)
|
||||
sam.DMAC.CHANNEL[s.dmaTxChannel()].CHCTRLA.Set((sam.DMAC_CHANNEL_CHCTRLA_TRIGACT_BURST << sam.DMAC_CHANNEL_CHCTRLA_TRIGACT_Pos) |
|
||||
(s.triggerSource() << sam.DMAC_CHANNEL_CHCTRLA_TRIGSRC_Pos) |
|
||||
(sam.DMAC_CHANNEL_CHCTRLA_BURSTLEN_SINGLE << sam.DMAC_CHANNEL_CHCTRLA_BURSTLEN_Pos))
|
||||
}
|
||||
|
||||
// For some reason, you have to provide the address just past the end of the
|
||||
// array instead of the address of the array.
|
||||
descriptor := &dmaDescriptorSection[s.dmaTxChannel()]
|
||||
descriptor.srcaddr = unsafe.Pointer(uintptr(unsafe.Pointer(&tx[0])) + uintptr(len(tx)))
|
||||
descriptor.btcnt = uint16(len(tx)) // beat count
|
||||
|
||||
// Start the transfer.
|
||||
sam.DMAC.CHANNEL[s.dmaTxChannel()].CHCTRLA.SetBits(sam.DMAC_CHANNEL_CHCTRLA_ENABLE)
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// Wait until all active transactions (started by StartTx) have finished. The
|
||||
// buffers provided in StartTx will be available after this method returns.
|
||||
func (spi SPI) Wait() error {
|
||||
// Wait until the previous SPI transfer completed.
|
||||
// This is basically the same thing as in SPI.tx.
|
||||
|
||||
// TODO: maybe block (and sleep) until the transfer has completed?
|
||||
|
||||
for !spi.Bus.INTFLAG.HasBits(sam.SERCOM_SPIM_INTFLAG_TXC) {
|
||||
}
|
||||
|
||||
// read to clear RXC register
|
||||
for spi.Bus.INTFLAG.HasBits(sam.SERCOM_SPIM_INTFLAG_RXC) {
|
||||
spi.Bus.DATA.Get()
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// The QSPI peripheral on ATSAMD51 is only available on the following pins
|
||||
const (
|
||||
QSPI_SCK = PB10
|
||||
|
||||
@@ -62,6 +62,27 @@ func setSERCOMClockGenerator(sercom uint8, gclk uint32) {
|
||||
}
|
||||
}
|
||||
|
||||
// DMA trigger source
|
||||
func (spi SPI) triggerSource() (tx uint32) {
|
||||
// See TRIGSRC field of CHCTRLA register for description of these constants.
|
||||
switch spi.Bus {
|
||||
case sam.SERCOM0_SPIM:
|
||||
return 0x05
|
||||
case sam.SERCOM1_SPIM:
|
||||
return 0x07
|
||||
case sam.SERCOM2_SPIM:
|
||||
return 0x09
|
||||
case sam.SERCOM3_SPIM:
|
||||
return 0x0A
|
||||
case sam.SERCOM4_SPIM:
|
||||
return 0x0C
|
||||
case sam.SERCOM5_SPIM:
|
||||
return 0x0E
|
||||
default:
|
||||
return 0 // should be unreachable
|
||||
}
|
||||
}
|
||||
|
||||
// This chip has three TCC peripherals, which have PWM as one feature.
|
||||
var (
|
||||
TCC0 = (*TCC)(sam.TCC0)
|
||||
|
||||
@@ -62,6 +62,27 @@ func setSERCOMClockGenerator(sercom uint8, gclk uint32) {
|
||||
}
|
||||
}
|
||||
|
||||
// DMA trigger source
|
||||
func (spi SPI) triggerSource() (tx uint32) {
|
||||
// See TRIGSRC field of CHCTRLA register for description of these constants.
|
||||
switch spi.Bus {
|
||||
case sam.SERCOM0_SPIM:
|
||||
return 0x05
|
||||
case sam.SERCOM1_SPIM:
|
||||
return 0x07
|
||||
case sam.SERCOM2_SPIM:
|
||||
return 0x09
|
||||
case sam.SERCOM3_SPIM:
|
||||
return 0x0B
|
||||
case sam.SERCOM4_SPIM:
|
||||
return 0x0D
|
||||
case sam.SERCOM5_SPIM:
|
||||
return 0x0F
|
||||
default:
|
||||
return 0 // should be unreachable
|
||||
}
|
||||
}
|
||||
|
||||
// This chip has five TCC peripherals, which have PWM as one feature.
|
||||
var (
|
||||
TCC0 = (*TCC)(sam.TCC0)
|
||||
|
||||
@@ -62,6 +62,27 @@ func setSERCOMClockGenerator(sercom uint8, gclk uint32) {
|
||||
}
|
||||
}
|
||||
|
||||
// DMA trigger source
|
||||
func (spi SPI) triggerSource() (tx uint32) {
|
||||
// See TRIGSRC field of CHCTRLA register for description of these constants.
|
||||
switch spi.Bus {
|
||||
case sam.SERCOM0_SPIM:
|
||||
return 0x05
|
||||
case sam.SERCOM1_SPIM:
|
||||
return 0x07
|
||||
case sam.SERCOM2_SPIM:
|
||||
return 0x09
|
||||
case sam.SERCOM3_SPIM:
|
||||
return 0x0A
|
||||
case sam.SERCOM4_SPIM:
|
||||
return 0x0C
|
||||
case sam.SERCOM5_SPIM:
|
||||
return 0x0E
|
||||
default:
|
||||
return 0 // should be unreachable
|
||||
}
|
||||
}
|
||||
|
||||
// This chip has five TCC peripherals, which have PWM as one feature.
|
||||
var (
|
||||
TCC0 = (*TCC)(sam.TCC0)
|
||||
|
||||
@@ -76,6 +76,31 @@ func setSERCOMClockGenerator(sercom uint8, gclk uint32) {
|
||||
}
|
||||
}
|
||||
|
||||
// DMA trigger source
|
||||
func (spi SPI) triggerSource() (tx uint32) {
|
||||
// See TRIGSRC field of CHCTRLA register for description of these constants.
|
||||
switch spi.Bus {
|
||||
case sam.SERCOM0_SPIM:
|
||||
return 0x05
|
||||
case sam.SERCOM1_SPIM:
|
||||
return 0x07
|
||||
case sam.SERCOM2_SPIM:
|
||||
return 0x09
|
||||
case sam.SERCOM3_SPIM:
|
||||
return 0x0A
|
||||
case sam.SERCOM4_SPIM:
|
||||
return 0x0C
|
||||
case sam.SERCOM5_SPIM:
|
||||
return 0x0E
|
||||
case sam.SERCOM6_SPIM:
|
||||
return 0x10
|
||||
case sam.SERCOM7_SPIM:
|
||||
return 0x12
|
||||
default:
|
||||
return 0 // should be unreachable
|
||||
}
|
||||
}
|
||||
|
||||
// This chip has five TCC peripherals, which have PWM as one feature.
|
||||
var (
|
||||
TCC0 = (*TCC)(sam.TCC0)
|
||||
|
||||
@@ -76,6 +76,31 @@ func setSERCOMClockGenerator(sercom uint8, gclk uint32) {
|
||||
}
|
||||
}
|
||||
|
||||
// DMA trigger source
|
||||
func (spi SPI) triggerSource() (tx uint32) {
|
||||
// See TRIGSRC field of CHCTRLA register for description of these constants.
|
||||
switch spi.Bus {
|
||||
case sam.SERCOM0_SPIM:
|
||||
return 0x05
|
||||
case sam.SERCOM1_SPIM:
|
||||
return 0x07
|
||||
case sam.SERCOM2_SPIM:
|
||||
return 0x09
|
||||
case sam.SERCOM3_SPIM:
|
||||
return 0x0A
|
||||
case sam.SERCOM4_SPIM:
|
||||
return 0x0C
|
||||
case sam.SERCOM5_SPIM:
|
||||
return 0x0E
|
||||
case sam.SERCOM6_SPIM:
|
||||
return 0x10
|
||||
case sam.SERCOM7_SPIM:
|
||||
return 0x12
|
||||
default:
|
||||
return 0 // should be unreachable
|
||||
}
|
||||
}
|
||||
|
||||
// This chip has five TCC peripherals, which have PWM as one feature.
|
||||
var (
|
||||
TCC0 = (*TCC)(sam.TCC0)
|
||||
|
||||
@@ -62,6 +62,27 @@ func setSERCOMClockGenerator(sercom uint8, gclk uint32) {
|
||||
}
|
||||
}
|
||||
|
||||
// DMA trigger source
|
||||
func (spi SPI) triggerSource() (tx uint32) {
|
||||
// See TRIGSRC field of CHCTRLA register for description of these constants.
|
||||
switch spi.Bus {
|
||||
case sam.SERCOM0_SPIM:
|
||||
return 0x05
|
||||
case sam.SERCOM1_SPIM:
|
||||
return 0x07
|
||||
case sam.SERCOM2_SPIM:
|
||||
return 0x09
|
||||
case sam.SERCOM3_SPIM:
|
||||
return 0x0A
|
||||
case sam.SERCOM4_SPIM:
|
||||
return 0x0C
|
||||
case sam.SERCOM5_SPIM:
|
||||
return 0x0E
|
||||
default:
|
||||
return 0 // should be unreachable
|
||||
}
|
||||
}
|
||||
|
||||
// This chip has five TCC peripherals, which have PWM as one feature.
|
||||
var (
|
||||
TCC0 = (*TCC)(sam.TCC0)
|
||||
|
||||
@@ -76,6 +76,31 @@ func setSERCOMClockGenerator(sercom uint8, gclk uint32) {
|
||||
}
|
||||
}
|
||||
|
||||
// DMA trigger source
|
||||
func (spi SPI) triggerSource() (tx uint32) {
|
||||
// See TRIGSRC field of CHCTRLA register for description of these constants.
|
||||
switch spi.Bus {
|
||||
case sam.SERCOM0_SPIM:
|
||||
return 0x05
|
||||
case sam.SERCOM1_SPIM:
|
||||
return 0x07
|
||||
case sam.SERCOM2_SPIM:
|
||||
return 0x09
|
||||
case sam.SERCOM3_SPIM:
|
||||
return 0x0A
|
||||
case sam.SERCOM4_SPIM:
|
||||
return 0x0C
|
||||
case sam.SERCOM5_SPIM:
|
||||
return 0x0E
|
||||
case sam.SERCOM6_SPIM:
|
||||
return 0x10
|
||||
case sam.SERCOM7_SPIM:
|
||||
return 0x12
|
||||
default:
|
||||
return 0 // should be unreachable
|
||||
}
|
||||
}
|
||||
|
||||
// This chip has five TCC peripherals, which have PWM as one feature.
|
||||
var (
|
||||
TCC0 = (*TCC)(sam.TCC0)
|
||||
|
||||
@@ -289,7 +289,26 @@ func (spi SPI) isBusy() bool {
|
||||
|
||||
// tx writes buffer to SPI ignoring Rx.
|
||||
func (spi SPI) tx(tx []byte) error {
|
||||
if len(tx) == 0 {
|
||||
err := spi.StartTx(tx, nil)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return spi.Wait()
|
||||
}
|
||||
|
||||
// IsAsync returns whether the SPI supports async operation (usually DMA).
|
||||
//
|
||||
// It returns true on the rp2040.
|
||||
func (spi SPI) IsAsync() bool {
|
||||
return true
|
||||
}
|
||||
|
||||
// Start a transfer in the background.
|
||||
//
|
||||
// After this, another StartTx() or Wait() must be called. The provided byte
|
||||
// slices (tx and rx) may only be accessed again after Wait() was called.
|
||||
func (spi SPI) StartTx(tx, rx []byte) error {
|
||||
if len(tx) == 0 && len(rx) == 0 {
|
||||
// We don't have to do anything.
|
||||
// This avoids a panic in &tx[0] when len(tx) == 0.
|
||||
return nil
|
||||
@@ -306,6 +325,15 @@ func (spi SPI) tx(tx []byte) error {
|
||||
dreq = 18 // DREQ_SPI1_TX
|
||||
}
|
||||
|
||||
// Wait for the previous transmission to complete.
|
||||
for ch.CTRL_TRIG.Get()&rp.DMA_CH0_CTRL_TRIG_BUSY != 0 {
|
||||
}
|
||||
|
||||
if len(rx) != 0 {
|
||||
// Fallback. We don't support receiving data using DMA yet.
|
||||
return spi.Tx(tx, rx)
|
||||
}
|
||||
|
||||
// Configure the DMA peripheral as follows:
|
||||
// - set read address, write address, and number of transfer units (bytes)
|
||||
// - increment read address (in memory), don't increment write address (SSPDR)
|
||||
@@ -319,6 +347,19 @@ func (spi SPI) tx(tx []byte) error {
|
||||
rp.DMA_CH0_CTRL_TRIG_DATA_SIZE_SIZE_BYTE<<rp.DMA_CH0_CTRL_TRIG_DATA_SIZE_Pos |
|
||||
dreq<<rp.DMA_CH0_CTRL_TRIG_TREQ_SEL_Pos |
|
||||
rp.DMA_CH0_CTRL_TRIG_EN)
|
||||
return nil
|
||||
}
|
||||
|
||||
// Wait until all active transactions (started by StartTx) have finished. The
|
||||
// buffers provided in StartTx will be available after this method returns.
|
||||
func (spi SPI) Wait() error {
|
||||
// Pick the DMA channel reserved for this SPI peripheral.
|
||||
var ch *dmaChannel
|
||||
if spi.Bus == rp.SPI0 {
|
||||
ch = &dmaChannels[spi0DMAChannel]
|
||||
} else { // SPI1
|
||||
ch = &dmaChannels[spi1DMAChannel]
|
||||
}
|
||||
|
||||
// Wait until the transfer is complete.
|
||||
// TODO: do this more efficiently:
|
||||
|
||||
@@ -0,0 +1,29 @@
|
||||
//go:build !baremetal || atmega || esp32 || fe310 || k210 || nrf || (nxp && !mk66f18) || atsamd21 || (stm32 && !stm32f7x2 && !stm32l5x2)
|
||||
|
||||
package machine
|
||||
|
||||
// This is a non-async implementation of the async SPI calls.
|
||||
// It is useful for devices that don't support DMA on SPI, or for which it
|
||||
// hasn't been implemented yet.
|
||||
|
||||
// IsAsync returns whether the SPI supports async operation (usually DMA).
|
||||
//
|
||||
// This SPI does not support async operations.
|
||||
func (s SPI) IsAsync() bool {
|
||||
return false
|
||||
}
|
||||
|
||||
// Start a transfer in the background.
|
||||
//
|
||||
// Because this SPI implementation doesn't support async operation, it is an
|
||||
// alias for Tx.
|
||||
func (s SPI) StartTx(tx, rx []byte) error {
|
||||
return s.Tx(tx, rx)
|
||||
}
|
||||
|
||||
// Wait until all active transactions (started by StartTx) have finished.
|
||||
//
|
||||
// This is a no-op on this SPI implementation.
|
||||
func (s SPI) Wait() error {
|
||||
return nil
|
||||
}
|
||||
@@ -26,4 +26,7 @@ var _ interface { // 2
|
||||
Configure(config SPIConfig) error
|
||||
Tx(w, r []byte) error
|
||||
Transfer(w byte) (byte, error)
|
||||
IsAsync() bool
|
||||
StartTx(tx, rx []byte) error
|
||||
Wait() error
|
||||
} = (*SPI)(nil)
|
||||
|
||||
Reference in New Issue
Block a user