From 3eb33dff5d674ba7216aaa0a879a9a36eef954ef Mon Sep 17 00:00:00 2001 From: ardnew <3837367+ardnew@users.noreply.github.com> Date: Wed, 14 Oct 2020 11:00:24 -0500 Subject: [PATCH] feather-stm32f405: add I2C support (#1378) * machine/stm32f405: add initial I2C support --- src/machine/board_feather-stm32f405.go | 24 +- src/machine/i2c.go | 2 +- src/machine/machine_stm32_i2c.go | 481 +++++++++++++++++++++++++ src/machine/machine_stm32f405.go | 71 ++++ 4 files changed, 573 insertions(+), 5 deletions(-) create mode 100644 src/machine/machine_stm32_i2c.go diff --git a/src/machine/board_feather-stm32f405.go b/src/machine/board_feather-stm32f405.go index 3566591f..29c7d35d 100644 --- a/src/machine/board_feather-stm32f405.go +++ b/src/machine/board_feather-stm32f405.go @@ -202,11 +202,11 @@ const ( // #===========#==========#==============#==============#=======#=======# // | Interface | Hardware | Bus(Freq) | SDA/SCL Pins | AltFn | Alias | // #===========#==========#==============#==============#=======#=======# - // | I2C1 | I2C1 | | D14/D15 | | ~ | - // | I2C2 | I2C2 | | D0/D1 | | ~ | - // | I2C3 | I2C1 | | D9/D10 | | ~ | + // | I2C1 | I2C1 | APB1(42 MHz) | D14/D15 | 4 | ~ | + // | I2C2 | I2C2 | APB1(42 MHz) | D0/D1 | 4 | ~ | + // | I2C3 | I2C1 | APB1(42 MHz) | D9/D10 | 4 | ~ | // | --------- | -------- | ------------ | ------------ | ----- | ----- | - // | I2C0 | I2C1 | | D14/D15 | | I2C1 | + // | I2C0 | I2C1 | APB1(42 MHz) | D14/D15 | 4 | I2C1 | // #===========#==========#==============#==============#=======#=======# NUM_I2C_INTERFACES = 3 @@ -226,4 +226,20 @@ const ( I2C_SCL_PIN = I2C0_SCL_PIN // ) +var ( + I2C1 = I2C{ + Bus: stm32.I2C1, + AltFuncSelector: stm32.AF4_I2C1_2_3, + } + I2C2 = I2C{ + Bus: stm32.I2C2, + AltFuncSelector: stm32.AF4_I2C1_2_3, + } + I2C3 = I2C{ + Bus: stm32.I2C1, + AltFuncSelector: stm32.AF4_I2C1_2_3, + } + I2C0 = I2C1 +) + func initI2C() {} diff --git a/src/machine/i2c.go b/src/machine/i2c.go index b4650606..8b2862af 100644 --- a/src/machine/i2c.go +++ b/src/machine/i2c.go @@ -1,4 +1,4 @@ -// +build avr nrf sam stm32,!stm32f4 fe310 k210 +// +build avr nrf sam stm32,!stm32f407 fe310 k210 package machine diff --git a/src/machine/machine_stm32_i2c.go b/src/machine/machine_stm32_i2c.go new file mode 100644 index 00000000..68133a6e --- /dev/null +++ b/src/machine/machine_stm32_i2c.go @@ -0,0 +1,481 @@ +// +build stm32,!stm32f103xx,!stm32f407 + +package machine + +// Peripheral abstraction layer for I2C on the stm32 family + +import ( + "device/stm32" + "unsafe" +) + +const ( + flagOVR = 0x00010800 + flagAF = 0x00010400 + flagARLO = 0x00010200 + flagBERR = 0x00010100 + flagTXE = 0x00010080 + flagRXNE = 0x00010040 + flagSTOPF = 0x00010010 + flagADD10 = 0x00010008 + flagBTF = 0x00010004 + flagADDR = 0x00010002 + flagSB = 0x00010001 + flagDUALF = 0x00100080 + flagGENCALL = 0x00100010 + flagTRA = 0x00100004 + flagBUSY = 0x00100002 + flagMSL = 0x00100001 +) + +func (i2c I2C) hasFlag(flag uint32) bool { + const mask = 0x0000FFFF + if uint8(flag>>16) == 1 { + return i2c.Bus.SR1.HasBits(flag & mask) + } else { + return i2c.Bus.SR2.HasBits(flag & mask) + } +} + +func (i2c I2C) clearFlag(flag uint32) { + const mask = 0x0000FFFF + i2c.Bus.SR1.Set(^(flag & mask)) +} + +// clearFlagADDR reads both status registers to clear any pending ADDR flags. +func (i2c I2C) clearFlagADDR() { + i2c.Bus.SR1.Get() + i2c.Bus.SR2.Get() +} + +func (i2c I2C) waitForFlag(flag uint32, set bool) bool { + const tryMax = 10000 + hasFlag := false + for i := 0; !hasFlag && i < tryMax; i++ { + hasFlag = i2c.hasFlag(flag) == set + } + return hasFlag +} + +func (i2c I2C) waitForFlagOrError(flag uint32, set bool) bool { + const tryMax = 10000 + hasFlag := false + for i := 0; !hasFlag && i < tryMax; i++ { + if hasFlag = i2c.hasFlag(flag) == set; !hasFlag { + // check for ACK failure + if i2c.hasFlag(flagAF) { + // generate stop condition + i2c.Bus.CR1.SetBits(stm32.I2C_CR1_STOP) + // clear pending flags + i2c.clearFlag(flagAF) + return false + } else if i2c.hasFlag(flagSTOPF) { + // clear stop flag + i2c.clearFlag(flagSTOPF) + return false + } + } + } + return hasFlag +} + +type transferOption uint32 + +const ( + frameFirst = 0x00000001 + frameFirstAndNext = 0x00000002 + frameNext = 0x00000004 + frameFirstAndLast = 0x00000008 + frameLastNoStop = 0x00000010 + frameLast = 0x00000020 + frameNoOption = 0xFFFF0000 +) + +// addressable represents a type that can provide fully-formatted I2C peripheral +// addresses for both read operations and write operations. +type addressable interface { + toRead() uint32 + toWrite() uint32 + bitSize() uint8 +} + +// address7Bit and address10Bit stores the unshifted original I2C peripheral address +// in an unsigned integral data type and implements the addressable interface +// to reformat addresses as required for read/write operations. +// TODO: +// add 10-bit address support +type ( + address7Bit uint8 + //address10Bit uint16 +) + +func (sa address7Bit) toRead() uint32 { + return uint32(((uint8(sa) << 1) | uint8(stm32.I2C_OAR1_ADD0)) & 0xFF) +} +func (sa address7Bit) toWrite() uint32 { + return uint32(((uint8(sa) << 1) & ^(uint8(stm32.I2C_OAR1_ADD0))) & 0xFF) +} +func (sa address7Bit) bitSize() uint8 { return 7 } // 7-bit addresses + +//func (sa address10Bit) toRead() uint32 {} +//func (sa address10Bit) toWrite() uint32 {} +//func (sa address10Bit) bitSize() uint8 { return 10 } // 10-bit addresses + +func readAddress7Bit(addr uint8) uint32 { return address7Bit(addr).toRead() } +func writeAddress7Bit(addr uint8) uint32 { return address7Bit(addr).toWrite() } + +//func readAddress10Bit(addr uint16) uint32 { return address10Bit(addr).toRead() } +//func writeAddress10Bit(addr uint16) uint32 { return address10Bit(addr).toWrite() } + +// I2C fast mode (Fm) duty cycle +const ( + DutyCycle2 = 0 + DutyCycle16x9 = 1 +) + +// I2CConfig is used to store config info for I2C. +type I2CConfig struct { + Frequency uint32 + SCL Pin + SDA Pin + DutyCycle uint8 +} + +// Configure is intended to setup the STM32 I2C interface. +func (i2c I2C) Configure(config I2CConfig) { + + // The following is the required sequence in controller mode. + // 1. Program the peripheral input clock in I2C_CR2 Register in order to + // generate correct timings + // 2. Configure the clock control registers + // 3. Configure the rise time register + // 4. Program the I2C_CR1 register to enable the peripheral + // 5. Set the START bit in the I2C_CR1 register to generate a Start condition + + // disable I2C interface before any configuration changes + i2c.Bus.CR1.ClearBits(stm32.I2C_CR1_PE) + + // reset I2C bus + i2c.Bus.CR1.SetBits(stm32.I2C_CR1_SWRST) + i2c.Bus.CR1.ClearBits(stm32.I2C_CR1_SWRST) + + // 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) + + // default to 100 kHz (Sm, standard mode) if no frequency is set + if config.Frequency == 0 { + config.Frequency = TWI_FREQ_100KHZ + } + + // configure I2C input clock + i2c.Bus.CR2.SetBits(i2c.getFreqRange(config)) + + // configure rise time + i2c.Bus.TRISE.Set(i2c.getRiseTime(config)) + + // configure clock control + i2c.Bus.CCR.Set(i2c.getSpeed(config)) + + // disable GeneralCall and NoStretch modes + i2c.Bus.CR1.ClearBits(stm32.I2C_CR1_ENGC | stm32.I2C_CR1_NOSTRETCH) + + // enable I2C interface + i2c.Bus.CR1.ClearBits(stm32.I2C_CR1_PE) +} + +func (i2c I2C) Tx(addr uint16, w, r []byte) error { + a := address7Bit(addr) + if err := i2c.controllerTransmit(a, w); nil != err { + return err + } + if err := i2c.controllerReceive(a, r); nil != err { + return err + } + return nil +} + +func (i2c I2C) controllerTransmit(addr addressable, w []byte) error { + + if !i2c.waitForFlag(flagBUSY, false) { + return errI2CBusReadyTimeout + } + + // ensure peripheral is enabled + if !i2c.Bus.CR1.HasBits(stm32.I2C_CR1_PE) { + i2c.Bus.CR1.SetBits(stm32.I2C_CR1_PE) + } + + // disable POS + i2c.Bus.CR1.ClearBits(stm32.I2C_CR1_POS) + + pos := 0 + rem := len(w) + + // send peripheral address + if err := i2c.controllerRequestWrite(addr, frameNoOption); nil != err { + return err + } + + // clear ADDR flag + i2c.clearFlagADDR() + + for rem > 0 { + // wait for TXE flag set + if !i2c.waitForFlagOrError(flagTXE, true) { + return errI2CAckExpected + } + + // write data to DR + i2c.Bus.DR.Set(uint32(w[pos])) + // update counters + pos++ + rem-- + + if i2c.hasFlag(flagBTF) && rem != 0 { + // write data to DR + i2c.Bus.DR.Set(uint32(w[pos])) + // update counters + pos++ + rem-- + } + + // wait for transfer finished flag BTF set + if !i2c.waitForFlagOrError(flagBTF, true) { + return errI2CWriteTimeout + } + } + + // generate stop condition + i2c.Bus.CR1.SetBits(stm32.I2C_CR1_STOP) + + return nil +} + +func (i2c I2C) controllerRequestWrite(addr addressable, option transferOption) error { + + if frameFirstAndLast == option || frameFirst == option || frameNoOption == option { + // generate start condition + i2c.Bus.CR1.SetBits(stm32.I2C_CR1_START) + } else if false /* (hi2c->PreviousState == I2C_STATE_MASTER_BUSY_RX) */ { + // generate restart condition + i2c.Bus.CR1.SetBits(stm32.I2C_CR1_START) + } + + // ensure start bit is set + if !i2c.waitForFlag(flagSB, true) { + return errI2CSignalStartTimeout + } + + // send peripheral address + switch addr.bitSize() { + case 7: // 7-bit peripheral address + i2c.Bus.DR.Set(addr.toWrite()) + + case 10: // 10-bit peripheral address + // TODO + } + + // wait for address ACK from peripheral + if !i2c.waitForFlagOrError(flagADDR, true) { + return errI2CSignalStartTimeout + } + + return nil +} + +func (i2c I2C) controllerReceive(addr addressable, r []byte) error { + + if !i2c.waitForFlag(flagBUSY, false) { + return errI2CBusReadyTimeout + } + + // ensure peripheral is enabled + if !i2c.Bus.CR1.HasBits(stm32.I2C_CR1_PE) { + i2c.Bus.CR1.SetBits(stm32.I2C_CR1_PE) + } + + // disable POS + i2c.Bus.CR1.ClearBits(stm32.I2C_CR1_POS) + + pos := 0 + rem := len(r) + + // send peripheral address + if err := i2c.controllerRequestRead(addr, frameNoOption); nil != err { + return err + } + + switch rem { + case 0: + // clear ADDR flag + i2c.clearFlagADDR() + // generate stop condition + i2c.Bus.CR1.SetBits(stm32.I2C_CR1_STOP) + + case 1: + // disable ACK + i2c.Bus.CR1.ClearBits(stm32.I2C_CR1_ACK) + // clear ADDR flag + i2c.clearFlagADDR() + // generate stop condition + i2c.Bus.CR1.SetBits(stm32.I2C_CR1_STOP) + + case 2: + // disable ACK + i2c.Bus.CR1.ClearBits(stm32.I2C_CR1_ACK) + // enable POS + i2c.Bus.CR1.SetBits(stm32.I2C_CR1_POS) + // clear ADDR flag + i2c.clearFlagADDR() + + default: + // enable ACK + i2c.Bus.CR1.SetBits(stm32.I2C_CR1_ACK) + // clear ADDR flag + i2c.clearFlagADDR() + } + + for rem > 0 { + switch rem { + case 1: + // wait until RXNE flag is set + if !i2c.waitForFlagOrError(flagRXNE, true) { + return errI2CReadTimeout + } + + // read data from DR + r[pos] = byte(i2c.Bus.DR.Get()) + + // update counters + pos++ + rem-- + + case 2: + // wait until transfer finished flag BTF is set + if !i2c.waitForFlag(flagBTF, true) { + return errI2CReadTimeout + } + + // generate stop condition + i2c.Bus.CR1.SetBits(stm32.I2C_CR1_STOP) + + // read data from DR + r[pos] = byte(i2c.Bus.DR.Get()) + + // update counters + pos++ + rem-- + + // read data from DR + r[pos] = byte(i2c.Bus.DR.Get()) + + // update counters + pos++ + rem-- + + case 3: + // wait until transfer finished flag BTF is set + if !i2c.waitForFlag(flagBTF, true) { + return errI2CReadTimeout + } + + // disable ACK + i2c.Bus.CR1.ClearBits(stm32.I2C_CR1_ACK) + + // read data from DR + r[pos] = byte(i2c.Bus.DR.Get()) + + // update counters + pos++ + rem-- + + // wait until transfer finished flag BTF is set + if !i2c.waitForFlag(flagBTF, true) { + return errI2CReadTimeout + } + + // generate stop condition + i2c.Bus.CR1.SetBits(stm32.I2C_CR1_STOP) + + // read data from DR + r[pos] = byte(i2c.Bus.DR.Get()) + + // update counters + pos++ + rem-- + + // read data from DR + r[pos] = byte(i2c.Bus.DR.Get()) + + // update counters + pos++ + rem-- + + default: + // wait until RXNE flag is set + if !i2c.waitForFlagOrError(flagRXNE, true) { + return errI2CReadTimeout + } + + // read data from DR + r[pos] = byte(i2c.Bus.DR.Get()) + + // update counters + pos++ + rem-- + + if i2c.hasFlag(flagBTF) { + // read data from DR + r[pos] = byte(i2c.Bus.DR.Get()) + + // update counters + pos++ + rem-- + } + } + } + + return nil +} + +func (i2c I2C) controllerRequestRead(addr addressable, option transferOption) error { + + // enable ACK + i2c.Bus.CR1.SetBits(stm32.I2C_CR1_ACK) + + if frameFirstAndLast == option || frameFirst == option || frameNoOption == option { + // generate start condition + i2c.Bus.CR1.SetBits(stm32.I2C_CR1_START) + } else if false /* (hi2c->PreviousState == I2C_STATE_MASTER_BUSY_TX) */ { + // generate restart condition + i2c.Bus.CR1.SetBits(stm32.I2C_CR1_START) + } + + // ensure start bit is set + if !i2c.waitForFlag(flagSB, true) { + return errI2CSignalStartTimeout + } + + // send peripheral address + switch addr.bitSize() { + case 7: // 7-bit peripheral address + i2c.Bus.DR.Set(addr.toRead()) + + case 10: // 10-bit peripheral address + // TODO + } + + // wait for address ACK from peripheral + if !i2c.waitForFlagOrError(flagADDR, true) { + return errI2CSignalStartTimeout + } + + return nil +} diff --git a/src/machine/machine_stm32f405.go b/src/machine/machine_stm32f405.go index 4709fa2c..a1b50107 100644 --- a/src/machine/machine_stm32f405.go +++ b/src/machine/machine_stm32f405.go @@ -95,3 +95,74 @@ type I2C struct { Bus *stm32.I2C_Type AltFuncSelector stm32.AltFunc } + +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) getFreqRange(config I2CConfig) uint32 { + // all I2C interfaces are on APB1 (42 MHz) + clock := CPUFrequency() / 4 + // convert to MHz + clock /= 1000000 + // must be between 2 MHz (or 4 MHz for fast mode (Fm)) and 50 MHz, inclusive + var min, max uint32 = 2, 50 + if config.Frequency > 10000 { + min = 4 // fast mode (Fm) + } + if clock < min { + clock = min + } else if clock > max { + clock = max + } + return clock << stm32.I2C_CR2_FREQ_Pos +} + +func (i2c I2C) getRiseTime(config I2CConfig) uint32 { + // These bits must be programmed with the maximum SCL rise time given in the + // I2C bus specification, incremented by 1. + // For instance: in Sm mode, the maximum allowed SCL rise time is 1000 ns. + // If, in the I2C_CR2 register, the value of FREQ[5:0] bits is equal to 0x08 + // and PCLK1 = 125 ns, therefore the TRISE[5:0] bits must be programmed with + // 09h (1000 ns / 125 ns = 8 + 1) + freqRange := i2c.getFreqRange(config) + if config.Frequency > 100000 { + // fast mode (Fm) adjustment + freqRange *= 300 + freqRange /= 1000 + } + return (freqRange + 1) << stm32.I2C_TRISE_TRISE_Pos +} + +func (i2c I2C) getSpeed(config I2CConfig) uint32 { + ccr := func(pclk uint32, freq uint32, coeff uint32) uint32 { + return (((pclk - 1) / (freq * coeff)) + 1) & stm32.I2C_CCR_CCR_Msk + } + sm := func(pclk uint32, freq uint32) uint32 { // standard mode (Sm) + if s := ccr(pclk, freq, 2); s < 4 { + return 4 + } else { + return s + } + } + fm := func(pclk uint32, freq uint32, duty uint8) uint32 { // fast mode (Fm) + if duty == DutyCycle2 { + return ccr(pclk, freq, 3) + } else { + return ccr(pclk, freq, 25) | stm32.I2C_CCR_DUTY + } + } + // all I2C interfaces are on APB1 (42 MHz) + clock := CPUFrequency() / 4 + if config.Frequency <= 100000 { + return sm(clock, config.Frequency) + } else { + s := fm(clock, config.Frequency, config.DutyCycle) + if (s & stm32.I2C_CCR_CCR_Msk) == 0 { + return 1 + } else { + return s | stm32.I2C_CCR_F_S + } + } +}