diff options
author | Ron Evans <[email protected]> | 2019-01-21 14:35:40 +0100 |
---|---|---|
committer | Ayke van Laethem <[email protected]> | 2019-01-21 21:54:43 +0100 |
commit | 3ebf464da25e91141e37a7f365bcb575e91081ff (patch) | |
tree | 4a53dba6025d1e3570f44a902194676b7ffee4c2 | |
parent | 38c5e384afcb6fde289aec84d21edb6ca847efab (diff) | |
download | tinygo-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.go | 6 | ||||
-rw-r--r-- | src/machine/i2c.go | 2 | ||||
-rw-r--r-- | src/machine/machine_atsamd21g18.go | 236 | ||||
-rw-r--r-- | src/runtime/runtime_atsamd21g18.go | 15 |
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() +} |