aboutsummaryrefslogtreecommitdiffhomepage
path: root/src
diff options
context:
space:
mode:
authorKenneth Bell <[email protected]>2021-03-20 21:39:04 -0700
committerRon Evans <[email protected]>2021-03-24 08:35:34 +0100
commit46a7993fb876c91a37575608f87cffaaa6fcd9c7 (patch)
tree73450974b8d5ba629fcba5c9a4f1fae07c86caf0 /src
parent9f3dcf3733a1b1c89214a0be2891d6a6ca36386b (diff)
downloadtinygo-46a7993fb876c91a37575608f87cffaaa6fcd9c7.tar.gz
tinygo-46a7993fb876c91a37575608f87cffaaa6fcd9c7.zip
stm32: i2c implementation for F7, L5 and L4 MCUs
Diffstat (limited to 'src')
-rw-r--r--src/machine/board_nucleof722ze.go13
-rw-r--r--src/machine/board_nucleol432kc.go14
-rw-r--r--src/machine/board_nucleol552ze.go14
-rw-r--r--src/machine/i2c.go2
-rw-r--r--src/machine/machine_stm32_i2c_reva.go (renamed from src/machine/machine_stm32_i2c.go)5
-rw-r--r--src/machine/machine_stm32_i2c_revb.go347
-rw-r--r--src/machine/machine_stm32f7x2.go10
-rw-r--r--src/machine/machine_stm32l4x2.go10
-rw-r--r--src/machine/machine_stm32l5x2.go10
-rw-r--r--src/runtime/runtime_stm32_timers.go1
10 files changed, 419 insertions, 7 deletions
diff --git a/src/machine/board_nucleof722ze.go b/src/machine/board_nucleof722ze.go
index eac0348c9..32a6e2564 100644
--- a/src/machine/board_nucleof722ze.go
+++ b/src/machine/board_nucleof722ze.go
@@ -54,6 +54,15 @@ const (
// I2C pins
const (
- SCL_PIN = PB6
- SDA_PIN = PB7
+ I2C0_SCL_PIN = PB8
+ I2C0_SDA_PIN = PB9
+)
+
+var (
+ // I2C1 is documented, alias to I2C0 as well
+ I2C1 = &I2C{
+ Bus: stm32.I2C1,
+ AltFuncSelector: 4,
+ }
+ I2C0 = I2C1
)
diff --git a/src/machine/board_nucleol432kc.go b/src/machine/board_nucleol432kc.go
index c9520aac2..a8cc31ac1 100644
--- a/src/machine/board_nucleol432kc.go
+++ b/src/machine/board_nucleol432kc.go
@@ -22,8 +22,9 @@ const (
// I2C pins
const (
- // PB6 and PB7 are mapped to CN4 pin 7 and CN4 pin 8 respectively with the
- // default solder bridge settings
+ // With default solder bridge settings:
+ // PB6 / Arduino D5 / CN3 Pin 8 is SCL
+ // PB7 / Arduino D4 / CN3 Pin 7 is SDA
I2C0_SCL_PIN = PB6
I2C0_SDA_PIN = PB7
)
@@ -41,6 +42,15 @@ var (
UART1 = &UART0
)
+var (
+ // I2C1 is documented, alias to I2C0 as well
+ I2C1 = &I2C{
+ Bus: stm32.I2C1,
+ AltFuncSelector: 4,
+ }
+ I2C0 = I2C1
+)
+
func init() {
UART0.Interrupt = interrupt.New(stm32.IRQ_USART2, UART0.handleInterrupt)
}
diff --git a/src/machine/board_nucleol552ze.go b/src/machine/board_nucleol552ze.go
index b97be2725..d7fc67e99 100644
--- a/src/machine/board_nucleol552ze.go
+++ b/src/machine/board_nucleol552ze.go
@@ -41,6 +41,20 @@ var (
UART1 = &UART0
)
+const (
+ I2C0_SCL_PIN = PB8
+ I2C0_SDA_PIN = PB9
+)
+
+var (
+ // I2C1 is documented, alias to I2C0 as well
+ I2C1 = &I2C{
+ Bus: stm32.I2C1,
+ AltFuncSelector: 4,
+ }
+ I2C0 = I2C1
+)
+
func init() {
UART0.Interrupt = interrupt.New(stm32.IRQ_LPUART1, UART0.handleInterrupt)
}
diff --git a/src/machine/i2c.go b/src/machine/i2c.go
index 93f1e41fe..25cf54c3c 100644
--- a/src/machine/i2c.go
+++ b/src/machine/i2c.go
@@ -1,4 +1,4 @@
-// +build avr nrf sam stm32,!stm32f7x2,!stm32l5x2,!stm32l0,!stm32l4x2 fe310 k210
+// +build avr nrf sam stm32,!stm32l0 fe310 k210
package machine
diff --git a/src/machine/machine_stm32_i2c.go b/src/machine/machine_stm32_i2c_reva.go
index c239a7973..fe087c069 100644
--- a/src/machine/machine_stm32_i2c.go
+++ b/src/machine/machine_stm32_i2c_reva.go
@@ -1,8 +1,9 @@
-// +build stm32,!stm32f7x2,!stm32l5x2,!stm32l0,!stm32l4x2
+// +build stm32f4 stm32f1
package machine
-// Peripheral abstraction layer for I2C on the stm32 family
+// I2C implementation for 'older' STM32 MCUs, including the F1 and F4 series
+// of MCUs.
import (
"device/stm32"
diff --git a/src/machine/machine_stm32_i2c_revb.go b/src/machine/machine_stm32_i2c_revb.go
new file mode 100644
index 000000000..bab69a933
--- /dev/null
+++ b/src/machine/machine_stm32_i2c_revb.go
@@ -0,0 +1,347 @@
+// +build stm32l5 stm32f7 stm32l4
+
+package machine
+
+import (
+ "device/stm32"
+ "unsafe"
+)
+
+//go:linkname ticks runtime.ticks
+func ticks() int64
+
+// I2C implementation for 'newer' STM32 MCUs, including the F7, L5 and L4
+// series of MCUs.
+//
+// Currently, only 100KHz mode is supported
+
+const (
+ flagBUSY = stm32.I2C_ISR_BUSY
+ flagTCR = stm32.I2C_ISR_TCR
+ flagRXNE = stm32.I2C_ISR_RXNE
+ flagSTOPF = stm32.I2C_ISR_STOPF
+ flagAF = stm32.I2C_ISR_NACKF
+ flagTXIS = stm32.I2C_ISR_TXIS
+ flagTXE = stm32.I2C_ISR_TXE
+)
+
+const (
+ MAX_NBYTE_SIZE = 255
+ TIMEOUT_TICKS = 100 // 100ms
+
+ I2C_NO_STARTSTOP = 0x0
+ I2C_GENERATE_START_WRITE = 0x80000000 | stm32.I2C_CR2_START
+ I2C_GENERATE_START_READ = 0x80000000 | stm32.I2C_CR2_START | stm32.I2C_CR2_RD_WRN
+ I2C_GENERATE_STOP = 0x80000000 | stm32.I2C_CR2_STOP
+)
+
+type I2C struct {
+ Bus *stm32.I2C_Type
+ AltFuncSelector uint8
+}
+
+// I2CConfig is used to store config info for I2C.
+type I2CConfig struct {
+ SCL Pin
+ SDA Pin
+}
+
+func (i2c I2C) Configure(config I2CConfig) error {
+ // disable I2C interface before any configuration changes
+ i2c.Bus.CR1.ClearBits(stm32.I2C_CR1_PE)
+
+ // enable clock for I2C
+ enableAltFuncClock(unsafe.Pointer(i2c.Bus))
+
+ // init pins
+ if config.SCL == 0 && config.SDA == 0 {
+ config.SCL = I2C0_SCL_PIN
+ config.SDA = I2C0_SDA_PIN
+ }
+ i2c.configurePins(config)
+
+ // Frequency range
+ i2c.Bus.TIMINGR.Set(i2c.getFreqRange())
+
+ // Disable Own Address1 before set the Own Address1 configuration
+ i2c.Bus.OAR1.ClearBits(stm32.I2C_OAR1_OA1EN)
+
+ // 7 bit addressing, no self address
+ i2c.Bus.OAR1.Set(stm32.I2C_OAR1_OA1EN)
+
+ // Enable the AUTOEND by default, and enable NACK (should be disable only during Slave process
+ i2c.Bus.CR2.Set(stm32.I2C_CR2_AUTOEND | stm32.I2C_CR2_NACK)
+
+ // Disable Own Address2 / Dual Addressing
+ i2c.Bus.OAR2.Set(0)
+
+ // Disable Generalcall and NoStretch, Enable peripheral
+ i2c.Bus.CR1.Set(stm32.I2C_CR1_PE)
+
+ return nil
+}
+
+func (i2c I2C) Tx(addr uint16, w, r []byte) error {
+ if len(w) > 0 {
+ if err := i2c.controllerTransmit(addr, w); nil != err {
+ return err
+ }
+ }
+
+ if len(r) > 0 {
+ if err := i2c.controllerReceive(addr, r); nil != err {
+ return err
+ }
+ }
+
+ return nil
+}
+
+func (i2c I2C) configurePins(config I2CConfig) {
+ config.SCL.ConfigureAltFunc(PinConfig{Mode: PinModeI2CSCL}, i2c.AltFuncSelector)
+ config.SDA.ConfigureAltFunc(PinConfig{Mode: PinModeI2CSDA}, i2c.AltFuncSelector)
+}
+
+func (i2c I2C) controllerTransmit(addr uint16, w []byte) error {
+ start := ticks()
+
+ if !i2c.waitOnFlagUntilTimeout(flagBUSY, false, start) {
+ return errI2CBusReadyTimeout
+ }
+
+ pos := 0
+ xferCount := len(w)
+ xferSize := uint8(xferCount)
+ if xferCount > MAX_NBYTE_SIZE {
+ // Large write, indicate reload
+ xferSize = MAX_NBYTE_SIZE
+ i2c.transferConfig(addr, xferSize, stm32.I2C_CR2_RELOAD, I2C_GENERATE_START_WRITE)
+ } else {
+ // Small write, auto-end
+ i2c.transferConfig(addr, xferSize, stm32.I2C_CR2_AUTOEND, I2C_GENERATE_START_WRITE)
+ }
+
+ for xferCount > 0 {
+ if !i2c.waitOnTXISFlagUntilTimeout(start) {
+ return errI2CWriteTimeout
+ }
+
+ i2c.Bus.TXDR.Set(uint32(w[pos]))
+ pos++
+ xferCount--
+ xferSize--
+
+ // If we've written the last byte of this chunk
+ if xferCount != 0 && xferSize == 0 {
+ // Wait for Transfer Complete Reload to be flagged
+ if !i2c.waitOnFlagUntilTimeout(flagTCR, true, start) {
+ return errI2CWriteTimeout
+ }
+
+ if xferCount > MAX_NBYTE_SIZE {
+ // Large write remaining, indicate reload
+ xferSize = MAX_NBYTE_SIZE
+ i2c.transferConfig(addr, xferSize, stm32.I2C_CR2_RELOAD, I2C_NO_STARTSTOP)
+ } else {
+ // Small write, auto-end
+ xferSize = uint8(xferCount)
+ i2c.transferConfig(addr, xferSize, stm32.I2C_CR2_AUTOEND, I2C_NO_STARTSTOP)
+ }
+ }
+ }
+
+ if !i2c.waitOnStopFlagUntilTimeout(start) {
+ return errI2CWriteTimeout
+ }
+
+ i2c.clearFlag(stm32.I2C_ISR_STOPF)
+
+ i2c.resetCR2()
+
+ return nil
+}
+
+func (i2c I2C) controllerReceive(addr uint16, r []byte) error {
+ start := ticks()
+
+ if !i2c.waitOnFlagUntilTimeout(flagBUSY, false, start) {
+ return errI2CBusReadyTimeout
+ }
+
+ pos := 0
+ xferCount := len(r)
+ xferSize := uint8(xferCount)
+ if xferCount > MAX_NBYTE_SIZE {
+ // Large read, indicate reload
+ xferSize = MAX_NBYTE_SIZE
+ i2c.transferConfig(addr, xferSize, stm32.I2C_CR2_RELOAD, I2C_GENERATE_START_READ)
+ } else {
+ // Small read, auto-end
+ i2c.transferConfig(addr, xferSize, stm32.I2C_CR2_AUTOEND, I2C_GENERATE_START_READ)
+ }
+
+ for xferCount > 0 {
+ if !i2c.waitOnRXNEFlagUntilTimeout(start) {
+ return errI2CWriteTimeout
+ }
+
+ r[pos] = uint8(i2c.Bus.RXDR.Get())
+ pos++
+ xferCount--
+ xferSize--
+
+ // If we've read the last byte of this chunk
+ if xferCount != 0 && xferSize == 0 {
+ // Wait for Transfer Complete Reload to be flagged
+ if !i2c.waitOnFlagUntilTimeout(flagTCR, true, start) {
+ return errI2CWriteTimeout
+ }
+
+ if xferCount > MAX_NBYTE_SIZE {
+ // Large read remaining, indicate reload
+ xferSize = MAX_NBYTE_SIZE
+ i2c.transferConfig(addr, xferSize, stm32.I2C_CR2_RELOAD, I2C_NO_STARTSTOP)
+ } else {
+ // Small read, auto-end
+ xferSize = uint8(xferCount)
+ i2c.transferConfig(addr, xferSize, stm32.I2C_CR2_AUTOEND, I2C_NO_STARTSTOP)
+ }
+ }
+ }
+
+ if !i2c.waitOnStopFlagUntilTimeout(start) {
+ return errI2CWriteTimeout
+ }
+
+ i2c.clearFlag(stm32.I2C_ISR_STOPF)
+
+ i2c.resetCR2()
+
+ return nil
+}
+
+func (i2c I2C) waitOnFlagUntilTimeout(flag uint32, set bool, startTicks int64) bool {
+ for i2c.hasFlag(flag) != set {
+ if (ticks() - startTicks) > TIMEOUT_TICKS {
+ return false
+ }
+ }
+ return true
+}
+
+func (i2c I2C) waitOnRXNEFlagUntilTimeout(startTicks int64) bool {
+ for !i2c.hasFlag(flagRXNE) {
+ if i2c.isAcknowledgeFailed(startTicks) {
+ return false
+ }
+
+ if i2c.hasFlag(flagSTOPF) {
+ i2c.clearFlag(flagSTOPF)
+ i2c.resetCR2()
+ return false
+ }
+
+ if (ticks() - startTicks) > TIMEOUT_TICKS {
+ return false
+ }
+ }
+
+ return true
+}
+
+func (i2c I2C) waitOnTXISFlagUntilTimeout(startTicks int64) bool {
+ for !i2c.hasFlag(flagTXIS) {
+ if i2c.isAcknowledgeFailed(startTicks) {
+ return false
+ }
+
+ if (ticks() - startTicks) > TIMEOUT_TICKS {
+ return false
+ }
+ }
+
+ return true
+}
+
+func (i2c I2C) waitOnStopFlagUntilTimeout(startTicks int64) bool {
+ for !i2c.hasFlag(flagSTOPF) {
+ if i2c.isAcknowledgeFailed(startTicks) {
+ return false
+ }
+
+ if (ticks() - startTicks) > TIMEOUT_TICKS {
+ return false
+ }
+ }
+
+ return true
+}
+
+func (i2c I2C) isAcknowledgeFailed(startTicks int64) bool {
+ if i2c.hasFlag(flagAF) {
+ // Wait until STOP Flag is reset
+ // AutoEnd should be initiate after AF
+ for !i2c.hasFlag(flagSTOPF) {
+ if (ticks() - startTicks) > TIMEOUT_TICKS {
+ return true
+ }
+ }
+
+ i2c.clearFlag(flagAF)
+ i2c.clearFlag(flagSTOPF)
+ i2c.flushTXDR()
+ i2c.resetCR2()
+
+ return true
+ }
+
+ return false
+}
+
+func (i2c I2C) flushTXDR() {
+ // If a pending TXIS flag is set, write a dummy data in TXDR to clear it
+ if i2c.hasFlag(flagTXIS) {
+ i2c.Bus.TXDR.Set(0)
+ }
+
+ // Flush TX register if not empty
+ if !i2c.hasFlag(flagTXE) {
+ i2c.clearFlag(flagTXE)
+ }
+}
+
+func (i2c I2C) resetCR2() {
+ i2c.Bus.CR2.ClearBits(stm32.I2C_CR2_SADD_Msk |
+ stm32.I2C_CR2_HEAD10R_Msk |
+ stm32.I2C_CR2_NBYTES_Msk |
+ stm32.I2C_CR2_RELOAD_Msk |
+ stm32.I2C_CR2_RD_WRN_Msk)
+}
+
+func (i2c I2C) transferConfig(addr uint16, size uint8, mode uint32, request uint32) {
+ mask := uint32(stm32.I2C_CR2_SADD_Msk |
+ stm32.I2C_CR2_NBYTES_Msk |
+ stm32.I2C_CR2_RELOAD_Msk |
+ stm32.I2C_CR2_AUTOEND_Msk |
+ (stm32.I2C_CR2_RD_WRN & uint32(request>>(31-stm32.I2C_CR2_RD_WRN_Pos))) |
+ stm32.I2C_CR2_START_Msk |
+ stm32.I2C_CR2_STOP_Msk)
+
+ value := (uint32(addr<<1) & stm32.I2C_CR2_SADD_Msk) |
+ ((uint32(size) << stm32.I2C_CR2_NBYTES_Pos) & stm32.I2C_CR2_NBYTES_Msk) |
+ mode | request
+
+ i2c.Bus.CR2.ReplaceBits(value, mask, 0)
+}
+
+func (i2c I2C) hasFlag(flag uint32) bool {
+ return i2c.Bus.ISR.HasBits(flag)
+}
+
+func (i2c I2C) clearFlag(flag uint32) {
+ if flag == stm32.I2C_ISR_TXE {
+ i2c.Bus.ISR.SetBits(flag)
+ } else {
+ i2c.Bus.ICR.SetBits(flag)
+ }
+}
diff --git a/src/machine/machine_stm32f7x2.go b/src/machine/machine_stm32f7x2.go
index 8038fa7a0..f9488d2ff 100644
--- a/src/machine/machine_stm32f7x2.go
+++ b/src/machine/machine_stm32f7x2.go
@@ -41,3 +41,13 @@ func (uart *UART) setRegisters() {
uart.statusReg = &uart.Bus.ISR
uart.txEmptyFlag = stm32.USART_ISR_TXE
}
+
+//---------- I2C related code
+
+// Gets the value for TIMINGR register
+func (i2c I2C) getFreqRange() uint32 {
+ // This is a 'magic' value calculated by STM32CubeMX
+ // for 27MHz PCLK1 (216MHz CPU Freq / 8).
+ // TODO: Do calculations based on PCLK1
+ return 0x00606A9B
+}
diff --git a/src/machine/machine_stm32l4x2.go b/src/machine/machine_stm32l4x2.go
index 0320e7648..8baf6e571 100644
--- a/src/machine/machine_stm32l4x2.go
+++ b/src/machine/machine_stm32l4x2.go
@@ -34,3 +34,13 @@ func (uart *UART) setRegisters() {
uart.statusReg = &uart.Bus.ISR
uart.txEmptyFlag = stm32.USART_ISR_TXE
}
+
+//---------- I2C related code
+
+// Gets the value for TIMINGR register
+func (i2c I2C) getFreqRange() uint32 {
+ // This is a 'magic' value calculated by STM32CubeMX
+ // for 80MHz PCLK1.
+ // TODO: Do calculations based on PCLK1
+ return 0x10909CEC
+}
diff --git a/src/machine/machine_stm32l5x2.go b/src/machine/machine_stm32l5x2.go
index 7d31396f6..e909de3a7 100644
--- a/src/machine/machine_stm32l5x2.go
+++ b/src/machine/machine_stm32l5x2.go
@@ -39,3 +39,13 @@ func (uart *UART) setRegisters() {
uart.statusReg = &uart.Bus.ISR
uart.txEmptyFlag = stm32.USART_ISR_TXE
}
+
+//---------- I2C related code
+
+// Gets the value for TIMINGR register
+func (i2c I2C) getFreqRange() uint32 {
+ // This is a 'magic' value calculated by STM32CubeMX
+ // for 110MHz PCLK1.
+ // TODO: Do calculations based on PCLK1
+ return 0x40505681
+}
diff --git a/src/runtime/runtime_stm32_timers.go b/src/runtime/runtime_stm32_timers.go
index eb0aeedf1..589995d7f 100644
--- a/src/runtime/runtime_stm32_timers.go
+++ b/src/runtime/runtime_stm32_timers.go
@@ -56,6 +56,7 @@ func nanosecondsToTicks(ns int64) timeUnit {
}
// number of ticks (microseconds) since start.
+//go:linkname ticks runtime.ticks
func ticks() timeUnit {
return timeUnit(tickCount.Get())
}