diff options
author | Kenneth Bell <[email protected]> | 2021-03-20 21:39:04 -0700 |
---|---|---|
committer | Ron Evans <[email protected]> | 2021-03-24 08:35:34 +0100 |
commit | 46a7993fb876c91a37575608f87cffaaa6fcd9c7 (patch) | |
tree | 73450974b8d5ba629fcba5c9a4f1fae07c86caf0 /src | |
parent | 9f3dcf3733a1b1c89214a0be2891d6a6ca36386b (diff) | |
download | tinygo-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.go | 13 | ||||
-rw-r--r-- | src/machine/board_nucleol432kc.go | 14 | ||||
-rw-r--r-- | src/machine/board_nucleol552ze.go | 14 | ||||
-rw-r--r-- | src/machine/i2c.go | 2 | ||||
-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.go | 347 | ||||
-rw-r--r-- | src/machine/machine_stm32f7x2.go | 10 | ||||
-rw-r--r-- | src/machine/machine_stm32l4x2.go | 10 | ||||
-rw-r--r-- | src/machine/machine_stm32l5x2.go | 10 | ||||
-rw-r--r-- | src/runtime/runtime_stm32_timers.go | 1 |
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()) } |