aboutsummaryrefslogtreecommitdiffhomepage
diff options
context:
space:
mode:
authorRon Evans <[email protected]>2019-01-21 14:35:40 +0100
committerAyke van Laethem <[email protected]>2019-01-21 21:54:43 +0100
commit3ebf464da25e91141e37a7f365bcb575e91081ff (patch)
tree4a53dba6025d1e3570f44a902194676b7ffee4c2
parent38c5e384afcb6fde289aec84d21edb6ca847efab (diff)
downloadtinygo-3ebf464da25e91141e37a7f365bcb575e91081ff.tar.gz
tinygo-3ebf464da25e91141e37a7f365bcb575e91081ff.zip
machine/samd21: I2C implementation
Signed-off-by: Ron Evans <[email protected]>
-rw-r--r--src/machine/board_itsybitsy-m0.go6
-rw-r--r--src/machine/i2c.go2
-rw-r--r--src/machine/machine_atsamd21g18.go236
-rw-r--r--src/runtime/runtime_atsamd21g18.go15
4 files changed, 258 insertions, 1 deletions
diff --git a/src/machine/board_itsybitsy-m0.go b/src/machine/board_itsybitsy-m0.go
index cffa143f1..fca87b4f2 100644
--- a/src/machine/board_itsybitsy-m0.go
+++ b/src/machine/board_itsybitsy-m0.go
@@ -29,3 +29,9 @@ const (
UART_TX_PIN = D1
UART_RX_PIN = D0
)
+
+// I2C pins
+const (
+ SDA_PIN = 22 // // SDA: SERCOM3/PAD[0]
+ SCL_PIN = 23 // // SCL: SERCOM3/PAD[1]
+)
diff --git a/src/machine/i2c.go b/src/machine/i2c.go
index 7791cc655..c3dfe24c3 100644
--- a/src/machine/i2c.go
+++ b/src/machine/i2c.go
@@ -1,4 +1,4 @@
-// +build avr nrf stm32f103xx
+// +build avr nrf sam stm32f103xx
package machine
diff --git a/src/machine/machine_atsamd21g18.go b/src/machine/machine_atsamd21g18.go
index fd5a4af75..a742eb2b7 100644
--- a/src/machine/machine_atsamd21g18.go
+++ b/src/machine/machine_atsamd21g18.go
@@ -10,6 +10,7 @@ package machine
import (
"device/arm"
"device/sam"
+ "errors"
)
const CPU_FREQUENCY = 48000000
@@ -407,3 +408,238 @@ func handleUART0() {
bufferPut(byte((sam.SERCOM0_USART.DATA & 0xFF)))
sam.SERCOM0_USART.INTFLAG |= sam.SERCOM_USART_INTFLAG_RXC
}
+
+// I2C on the SAMD21.
+type I2C struct {
+ Bus *sam.SERCOM_I2CM_Type
+}
+
+// Since the I2C interfaces on the SAMD21 use the SERCOMx peripherals,
+// you can have multiple ones. we currently only implement one.
+var (
+ I2C0 = I2C{Bus: sam.SERCOM3_I2CM}
+)
+
+// I2CConfig is used to store config info for I2C.
+type I2CConfig struct {
+ Frequency uint32
+ SCL uint8
+ SDA uint8
+}
+
+const (
+ // Default rise time in nanoseconds, based on 4.7K ohm pull up resistors
+ riseTimeNanoseconds = 125
+
+ // wire bus states
+ wireUnknownState = 0
+ wireIdleState = 1
+ wireOwnerState = 2
+ wireBusyState = 3
+
+ // wire commands
+ wireCmdNoAction = 0
+ wireCmdRepeatStart = 1
+ wireCmdRead = 2
+ wireCmdStop = 3
+)
+
+const i2cTimeout = 1000
+
+// Configure is intended to setup the I2C interface.
+func (i2c I2C) Configure(config I2CConfig) {
+ // Default I2C bus speed is 100 kHz.
+ if config.Frequency == 0 {
+ config.Frequency = TWI_FREQ_100KHZ
+ }
+
+ // reset SERCOM3
+ i2c.Bus.CTRLA |= sam.SERCOM_I2CM_CTRLA_SWRST
+ for (i2c.Bus.CTRLA&sam.SERCOM_I2CM_CTRLA_SWRST) > 0 ||
+ (i2c.Bus.SYNCBUSY&sam.SERCOM_I2CM_SYNCBUSY_SWRST) > 0 {
+ }
+
+ // Set i2c master mode
+ //SERCOM_I2CM_CTRLA_MODE( I2C_MASTER_OPERATION )
+ i2c.Bus.CTRLA = (sam.SERCOM_I2CM_CTRLA_MODE_I2C_MASTER << sam.SERCOM_I2CM_CTRLA_MODE_Pos) // |
+
+ i2c.SetBaudRate(config.Frequency)
+
+ // Enable I2CM port.
+ // sercom->USART.CTRLA.bit.ENABLE = 0x1u;
+ i2c.Bus.CTRLA |= sam.SERCOM_I2CM_CTRLA_ENABLE
+ for (i2c.Bus.SYNCBUSY & sam.SERCOM_I2CM_SYNCBUSY_ENABLE) > 0 {
+ }
+
+ // set bus idle mode
+ i2c.Bus.STATUS |= (wireIdleState << sam.SERCOM_I2CM_STATUS_BUSSTATE_Pos)
+ for (i2c.Bus.SYNCBUSY & sam.SERCOM_I2CM_SYNCBUSY_SYSOP) > 0 {
+ }
+
+ // enable pins
+ GPIO{SDA_PIN}.Configure(GPIOConfig{Mode: GPIO_SERCOM})
+ GPIO{SCL_PIN}.Configure(GPIOConfig{Mode: GPIO_SERCOM})
+}
+
+// SetBaudRate sets the communication speed for the I2C.
+func (i2c I2C) SetBaudRate(br uint32) {
+ // Synchronous arithmetic baudrate, via Arduino SAMD implementation:
+ // SystemCoreClock / ( 2 * baudrate) - 5 - (((SystemCoreClock / 1000000) * WIRE_RISE_TIME_NANOSECONDS) / (2 * 1000));
+ baud := CPU_FREQUENCY/(2*br) - 5 - (((CPU_FREQUENCY / 1000000) * riseTimeNanoseconds) / (2 * 1000))
+ i2c.Bus.BAUD = sam.RegValue(baud)
+}
+
+// Tx does a single I2C transaction at the specified address.
+// It clocks out the given address, writes the bytes in w, reads back len(r)
+// bytes and stores them in r, and generates a stop condition on the bus.
+func (i2c I2C) Tx(addr uint16, w, r []byte) error {
+ var err error
+ if len(w) != 0 {
+ // send start/address for write
+ i2c.sendAddress(addr, true)
+
+ // wait until transmission complete
+ timeout := i2cTimeout
+ for (i2c.Bus.INTFLAG & sam.SERCOM_I2CM_INTFLAG_MB) == 0 {
+ timeout--
+ if timeout == 0 {
+ return errors.New("I2C timeout on ready to write data")
+ }
+ }
+
+ // ACK received (0: ACK, 1: NACK)
+ if (i2c.Bus.STATUS & sam.SERCOM_I2CM_STATUS_RXNACK) > 0 {
+ return errors.New("I2C write error: expected ACK not NACK")
+ }
+
+ // write data
+ for _, b := range w {
+ err = i2c.WriteByte(b)
+ if err != nil {
+ return err
+ }
+ }
+
+ err = i2c.signalStop()
+ if err != nil {
+ return err
+ }
+ }
+ if len(r) != 0 {
+ // send start/address for read
+ i2c.sendAddress(addr, false)
+
+ // wait transmission complete
+ for (i2c.Bus.INTFLAG & sam.SERCOM_I2CM_INTFLAG_SB) == 0 {
+ // If the slave NACKS the address, the MB bit will be set.
+ // In that case, send a stop condition and return error.
+ if (i2c.Bus.INTFLAG & sam.SERCOM_I2CM_INTFLAG_MB) > 0 {
+ i2c.Bus.CTRLB |= (wireCmdStop << sam.SERCOM_I2CM_CTRLB_CMD_Pos) // Stop condition
+ return errors.New("I2C read error: expected ACK not NACK")
+ }
+ }
+
+ // ACK received (0: ACK, 1: NACK)
+ if (i2c.Bus.STATUS & sam.SERCOM_I2CM_STATUS_RXNACK) > 0 {
+ return errors.New("I2C read error: expected ACK not NACK")
+ }
+
+ // read first byte
+ r[0] = i2c.readByte()
+ for i := 1; i < len(r); i++ {
+ // Send an ACK
+ i2c.Bus.CTRLB &^= sam.SERCOM_I2CM_CTRLB_ACKACT
+
+ i2c.signalRead()
+
+ // Read data and send the ACK
+ r[i] = i2c.readByte()
+ }
+
+ // Send NACK to end transmission
+ i2c.Bus.CTRLB |= sam.SERCOM_I2CM_CTRLB_ACKACT
+
+ err = i2c.signalStop()
+ if err != nil {
+ return err
+ }
+ }
+
+ return nil
+}
+
+// WriteByte writes a single byte to the I2C bus.
+func (i2c I2C) WriteByte(data byte) error {
+ // Send data byte
+ i2c.Bus.DATA = sam.RegValue8(data)
+
+ // wait until transmission successful
+ timeout := i2cTimeout
+ for (i2c.Bus.INTFLAG & sam.SERCOM_I2CM_INTFLAG_MB) == 0 {
+ // check for bus error
+ if (sam.SERCOM3_I2CM.STATUS & sam.SERCOM_I2CM_STATUS_BUSERR) > 0 {
+ return errors.New("I2C bus error")
+ }
+ timeout--
+ if timeout == 0 {
+ return errors.New("I2C timeout on write data")
+ }
+ }
+
+ if (i2c.Bus.STATUS & sam.SERCOM_I2CM_STATUS_RXNACK) > 0 {
+ return errors.New("I2C write error: expected ACK not NACK")
+ }
+
+ return nil
+}
+
+// sendAddress sends the address and start signal
+func (i2c I2C) sendAddress(address uint16, write bool) error {
+ data := (address << 1)
+ if !write {
+ data |= 1 // set read flag
+ }
+
+ // wait until bus ready
+ timeout := i2cTimeout
+ for (i2c.Bus.STATUS&(wireIdleState<<sam.SERCOM_I2CM_STATUS_BUSSTATE_Pos)) == 0 &&
+ (i2c.Bus.STATUS&(wireOwnerState<<sam.SERCOM_I2CM_STATUS_BUSSTATE_Pos)) == 0 {
+ timeout--
+ if timeout == 0 {
+ return errors.New("I2C timeout on bus ready")
+ }
+ }
+ i2c.Bus.ADDR = sam.RegValue(data)
+
+ return nil
+}
+
+func (i2c I2C) signalStop() error {
+ i2c.Bus.CTRLB |= (wireCmdStop << sam.SERCOM_I2CM_CTRLB_CMD_Pos) // Stop command
+ timeout := i2cTimeout
+ for (i2c.Bus.SYNCBUSY & sam.SERCOM_I2CM_SYNCBUSY_SYSOP) > 0 {
+ timeout--
+ if timeout == 0 {
+ return errors.New("I2C timeout on signal stop")
+ }
+ }
+ return nil
+}
+
+func (i2c I2C) signalRead() error {
+ i2c.Bus.CTRLB |= (wireCmdRead << sam.SERCOM_I2CM_CTRLB_CMD_Pos) // Read command
+ timeout := i2cTimeout
+ for (i2c.Bus.SYNCBUSY & sam.SERCOM_I2CM_SYNCBUSY_SYSOP) > 0 {
+ timeout--
+ if timeout == 0 {
+ return errors.New("I2C timeout on signal read")
+ }
+ }
+ return nil
+}
+
+func (i2c I2C) readByte() byte {
+ for (i2c.Bus.INTFLAG & sam.SERCOM_I2CM_INTFLAG_SB) == 0 {
+ }
+ return byte(i2c.Bus.DATA)
+}
diff --git a/src/runtime/runtime_atsamd21g18.go b/src/runtime/runtime_atsamd21g18.go
index 4bbb1f683..3682089d6 100644
--- a/src/runtime/runtime_atsamd21g18.go
+++ b/src/runtime/runtime_atsamd21g18.go
@@ -23,6 +23,7 @@ func init() {
initClocks()
initRTC()
initUARTClock()
+ initI2CClock()
// connect to UART
machine.UART0.Configure(machine.UARTConfig{})
@@ -306,3 +307,17 @@ func initUARTClock() {
sam.GCLK_CLKCTRL_CLKEN)
waitForSync()
}
+
+func initI2CClock() {
+ // Turn on clock to SERCOM3 for I2C0
+ sam.PM.APBCMASK |= sam.PM_APBCMASK_SERCOM3_
+
+ // Use GCLK0 for SERCOM3 aka I2C0
+ // GCLK_CLKCTRL_ID( clockId ) | // Generic Clock 0 (SERCOMx)
+ // GCLK_CLKCTRL_GEN_GCLK0 | // Generic Clock Generator 0 is source
+ // GCLK_CLKCTRL_CLKEN ;
+ sam.GCLK.CLKCTRL = sam.RegValue16((sam.GCLK_CLKCTRL_ID_SERCOM3_CORE << sam.GCLK_CLKCTRL_ID_Pos) |
+ (sam.GCLK_CLKCTRL_GEN_GCLK0 << sam.GCLK_CLKCTRL_GEN_Pos) |
+ sam.GCLK_CLKCTRL_CLKEN)
+ waitForSync()
+}