Skip to content

machine/samd21: Initial implementation of I2S hardware interface #331

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 1 commit into from
May 12, 2019
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .circleci/config.yml
Original file line number Diff line number Diff line change
Expand Up @@ -82,6 +82,7 @@ commands:
- run: tinygo build -size short -o test.elf -target=circuitplay-express examples/blinky1
- run: tinygo build -size short -o test.elf -target=stm32f4disco examples/blinky1
- run: tinygo build -size short -o test.elf -target=stm32f4disco examples/blinky2
- run: tinygo build -size short -o test.elf -target=circuitplay-express examples/i2s
- run: tinygo build -o wasm.wasm -target=wasm examples/wasm/export
- run: tinygo build -o wasm.wasm -target=wasm examples/wasm/main
test-linux:
Expand Down
25 changes: 25 additions & 0 deletions src/examples/i2s/i2s.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
// Example using the i2s hardware interface on the Adafruit Circuit Playground Express
// to read data from the onboard MEMS microphone.
//
package main

import (
"machine"
)

func main() {
machine.I2S0.Configure(machine.I2SConfig{
Mode: machine.I2SModePDM,
ClockSource: machine.I2SClockSourceExternal,
Stereo: true,
})

data := make([]uint32, 64)

for {
// get the next group of samples
machine.I2S0.Read(data)

println("data", data[0], data[1], data[2], data[4], "...")
}
}
12 changes: 12 additions & 0 deletions src/machine/board_circuitplay_express.go
Original file line number Diff line number Diff line change
Expand Up @@ -99,3 +99,15 @@ const (
var (
SPI0 = SPI{Bus: sam.SERCOM3_SPI}
)

// I2S pins
const (
I2S_SCK_PIN = PA10
I2S_SD_PIN = PA08
I2S_WS_PIN = 0xff // no WS, instead uses SCK to sync
)

// I2S on the Circuit Playground Express.
var (
I2S0 = I2S{Bus: sam.I2S}
)
12 changes: 12 additions & 0 deletions src/machine/board_itsybitsy-m0.go
Original file line number Diff line number Diff line change
Expand Up @@ -73,3 +73,15 @@ const (
var (
SPI0 = SPI{Bus: sam.SERCOM4_SPI}
)

// I2S pins
const (
I2S_SCK_PIN = PA10
I2S_SD_PIN = PA08
I2S_WS_PIN = 0xff // TODO: figure out what this is on ItsyBitsy M0.
)

// I2S on the ItsyBitsy M0.
var (
I2S0 = I2S{Bus: sam.I2S}
)
54 changes: 54 additions & 0 deletions src/machine/i2s.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
// +build sam

// This is the definition for I2S bus functions.
// Actual implementations if available for any given hardware
// are to be found in its the board definition.
//
// For more info about I2S, see: https://en.wikipedia.org/wiki/I%C2%B2S
//

package machine

type I2SMode uint8
type I2SStandard uint8
type I2SClockSource uint8
type I2SDataFormat uint8

const (
I2SModeMaster I2SMode = iota
I2SModeSlave
I2SModePDM
)

const (
I2StandardPhilips I2SStandard = iota
I2SStandardMSB
I2SStandardLSB
)

const (
I2SClockSourceInternal I2SClockSource = iota
I2SClockSourceExternal
)

const (
I2SDataFormatDefault I2SDataFormat = 0
I2SDataFormat8bit = 8
I2SDataFormat16bit = 16
I2SDataFormat24bit = 24
I2SDataFormat32bit = 32
)

// All fields are optional and may not be required or used on a particular platform.
type I2SConfig struct {
SCK uint8
WS uint8
SD uint8
Mode I2SMode
Standard I2SStandard
ClockSource I2SClockSource
DataFormat I2SDataFormat
AudioFrequency uint32
MasterClockOutput bool
Stereo bool
}
229 changes: 229 additions & 0 deletions src/machine/machine_atsamd21.go
Original file line number Diff line number Diff line change
Expand Up @@ -808,6 +808,235 @@ func (i2c I2C) readByte() byte {
return byte(i2c.Bus.DATA)
}

// I2S on the SAMD21.

// I2S
type I2S struct {
Bus *sam.I2S_Type
}

// Configure is used to configure the I2S interface. You must call this
// before you can use the I2S bus.
func (i2s I2S) Configure(config I2SConfig) {
// handle defaults
if config.SCK == 0 {
config.SCK = I2S_SCK_PIN
config.WS = I2S_WS_PIN
config.SD = I2S_SD_PIN
}

if config.AudioFrequency == 0 {
config.AudioFrequency = 48000
}

if config.DataFormat == I2SDataFormatDefault {
if config.Stereo {
config.DataFormat = I2SDataFormat16bit
} else {
config.DataFormat = I2SDataFormat32bit
}
}

// Turn on clock for I2S
sam.PM.APBCMASK |= sam.PM_APBCMASK_I2S_

// setting clock rate for sample.
division_factor := CPU_FREQUENCY / (config.AudioFrequency * uint32(config.DataFormat))

// Switch Generic Clock Generator 3 to DFLL48M.
sam.GCLK.GENDIV = sam.RegValue((sam.GCLK_CLKCTRL_GEN_GCLK3 << sam.GCLK_GENDIV_ID_Pos) |
(division_factor << sam.GCLK_GENDIV_DIV_Pos))
waitForSync()

sam.GCLK.GENCTRL = sam.RegValue((sam.GCLK_CLKCTRL_GEN_GCLK3 << sam.GCLK_GENCTRL_ID_Pos) |
(sam.GCLK_GENCTRL_SRC_DFLL48M << sam.GCLK_GENCTRL_SRC_Pos) |
sam.GCLK_GENCTRL_IDC |
sam.GCLK_GENCTRL_GENEN)
waitForSync()

// Use Generic Clock Generator 3 as source for I2S.
sam.GCLK.CLKCTRL = sam.RegValue16((sam.GCLK_CLKCTRL_ID_I2S_0 << sam.GCLK_CLKCTRL_ID_Pos) |
(sam.GCLK_CLKCTRL_GEN_GCLK3 << sam.GCLK_CLKCTRL_GEN_Pos) |
sam.GCLK_CLKCTRL_CLKEN)
waitForSync()

// reset the device
i2s.Bus.CTRLA |= sam.I2S_CTRLA_SWRST
for (i2s.Bus.SYNCBUSY & sam.I2S_SYNCBUSY_SWRST) > 0 {
}

// disable device before continuing
for (i2s.Bus.SYNCBUSY & sam.I2S_SYNCBUSY_ENABLE) > 0 {
}
i2s.Bus.CTRLA &^= sam.I2S_CTRLA_ENABLE

// setup clock
if config.ClockSource == I2SClockSourceInternal {
// TODO: make sure correct for I2S output

// set serial clock select pin
i2s.Bus.CLKCTRL0 |= sam.I2S_CLKCTRL_SCKSEL

// set frame select pin
i2s.Bus.CLKCTRL0 |= sam.I2S_CLKCTRL_FSSEL
} else {
// Configure FS generation from SCK clock.
i2s.Bus.CLKCTRL0 &^= sam.I2S_CLKCTRL_FSSEL
}

if config.Standard == I2StandardPhilips {
// set 1-bit delay
i2s.Bus.CLKCTRL0 |= sam.I2S_CLKCTRL_BITDELAY
} else {
// set 0-bit delay
i2s.Bus.CLKCTRL0 &^= sam.I2S_CLKCTRL_BITDELAY
}

// set number of slots.
if config.Stereo {
i2s.Bus.CLKCTRL0 |= (1 << sam.I2S_CLKCTRL_NBSLOTS_Pos)
} else {
i2s.Bus.CLKCTRL0 &^= (1 << sam.I2S_CLKCTRL_NBSLOTS_Pos)
}

// set slot size
switch config.DataFormat {
case I2SDataFormat8bit:
i2s.Bus.CLKCTRL0 |= sam.I2S_CLKCTRL_SLOTSIZE_8

case I2SDataFormat16bit:
i2s.Bus.CLKCTRL0 |= sam.I2S_CLKCTRL_SLOTSIZE_16

case I2SDataFormat24bit:
i2s.Bus.CLKCTRL0 |= sam.I2S_CLKCTRL_SLOTSIZE_24

case I2SDataFormat32bit:
i2s.Bus.CLKCTRL0 |= sam.I2S_CLKCTRL_SLOTSIZE_32
}

// configure pin for clock
GPIO{config.SCK}.Configure(GPIOConfig{Mode: GPIO_COM})

// configure pin for WS, if needed
if config.WS != 0xff {
GPIO{config.WS}.Configure(GPIOConfig{Mode: GPIO_COM})
}

// now set serializer data size.
switch config.DataFormat {
case I2SDataFormat8bit:
i2s.Bus.SERCTRL1 |= sam.I2S_SERCTRL_DATASIZE_8

case I2SDataFormat16bit:
i2s.Bus.SERCTRL1 |= sam.I2S_SERCTRL_DATASIZE_16

case I2SDataFormat24bit:
i2s.Bus.SERCTRL1 |= sam.I2S_SERCTRL_DATASIZE_24

case I2SDataFormat32bit:
case I2SDataFormatDefault:
i2s.Bus.SERCTRL1 |= sam.I2S_SERCTRL_DATASIZE_32
}

// set serializer slot adjustment
if config.Standard == I2SStandardLSB {
// adjust right
i2s.Bus.SERCTRL1 &^= sam.I2S_SERCTRL_SLOTADJ
} else {
// adjust left
i2s.Bus.SERCTRL1 |= sam.I2S_SERCTRL_SLOTADJ

// reverse bit order?
i2s.Bus.SERCTRL1 |= sam.I2S_SERCTRL_BITREV
}

// set serializer mode.
if config.Mode == I2SModePDM {
i2s.Bus.SERCTRL1 |= sam.I2S_SERCTRL_SERMODE_PDM2
} else {
i2s.Bus.SERCTRL1 |= sam.I2S_SERCTRL_SERMODE_RX
}

// configure data pin
GPIO{config.SD}.Configure(GPIOConfig{Mode: GPIO_COM})

// re-enable
i2s.Bus.CTRLA |= sam.I2S_CTRLA_ENABLE
for (i2s.Bus.SYNCBUSY & sam.I2S_SYNCBUSY_ENABLE) > 0 {
}

// enable i2s clock
i2s.Bus.CTRLA |= sam.I2S_CTRLA_CKEN0
for (i2s.Bus.SYNCBUSY & sam.I2S_SYNCBUSY_CKEN0) > 0 {
}

// enable i2s serializer
i2s.Bus.CTRLA |= sam.I2S_CTRLA_SEREN1
for (i2s.Bus.SYNCBUSY & sam.I2S_SYNCBUSY_SEREN1) > 0 {
}
}

// Read data from the I2S bus into the provided slice.
// The I2S bus must already have been configured correctly.
func (i2s I2S) Read(p []uint32) (n int, err error) {
i := 0
for i = 0; i < len(p); i++ {
// Wait until ready
for (i2s.Bus.INTFLAG & sam.I2S_INTFLAG_RXRDY1) == 0 {
}

for (i2s.Bus.SYNCBUSY & sam.I2S_SYNCBUSY_DATA1) > 0 {
}

// read data
p[i] = uint32(i2s.Bus.DATA1)

// indicate read complete
i2s.Bus.INTFLAG = sam.I2S_INTFLAG_RXRDY1
}

return i, nil
}

// Write data to the I2S bus from the provided slice.
// The I2S bus must already have been configured correctly.
func (i2s I2S) Write(p []uint32) (n int, err error) {
i := 0
for i = 0; i < len(p); i++ {
// Wait until ready
for (i2s.Bus.INTFLAG & sam.I2S_INTFLAG_TXRDY1) == 0 {
}

for (i2s.Bus.SYNCBUSY & sam.I2S_SYNCBUSY_DATA1) > 0 {
}

// write data
i2s.Bus.DATA1 = sam.RegValue(p[i])

// indicate write complete
i2s.Bus.INTFLAG = sam.I2S_INTFLAG_TXRDY1
}

return i, nil
}

// Close the I2S bus.
func (i2s I2S) Close() error {
// Sync wait
for (i2s.Bus.SYNCBUSY & sam.I2S_SYNCBUSY_ENABLE) > 0 {
}

// disable I2S
i2s.Bus.CTRLA &^= sam.I2S_CTRLA_ENABLE

return nil
}

func waitForSync() {
for (sam.GCLK.STATUS & sam.GCLK_STATUS_SYNCBUSY) > 0 {
}
}

// SPI
type SPI struct {
Bus *sam.SERCOM_SPI_Type
Expand Down