From 1ba463c7ee979f2a750a44de333880805a79a1f8 Mon Sep 17 00:00:00 2001 From: Ayke van Laethem Date: Sun, 11 Nov 2018 19:26:11 +0100 Subject: [PATCH] machine: redesign I2C interface --- src/examples/blinkm/blinkm.go | 11 +++-- src/machine/i2c.go | 23 +++++++++++ src/machine/machine_avr.go | 74 +++++++++++++++++---------------- src/machine/machine_nrf.go | 78 ++++++++++++++++++++++++----------- 4 files changed, 120 insertions(+), 66 deletions(-) diff --git a/src/examples/blinkm/blinkm.go b/src/examples/blinkm/blinkm.go index a175ab99..65a0f078 100644 --- a/src/examples/blinkm/blinkm.go +++ b/src/examples/blinkm/blinkm.go @@ -11,11 +11,10 @@ func main() { machine.I2C0.Configure(machine.I2CConfig{}) // Init BlinkM - machine.I2C0.WriteTo(0x09, []byte("o")) + machine.I2C0.WriteRegister(0x09, 'o', nil) version := []byte{0, 0} - machine.I2C0.WriteTo(0x09, []byte("Z")) - machine.I2C0.ReadFrom(0x09, version) + machine.I2C0.ReadRegister(0x09, 'Z', version) println("Firmware version:", string(version[0]), string(version[1])) count := 0 @@ -23,15 +22,15 @@ func main() { switch count { case 0: // Crimson - machine.I2C0.WriteTo(0x09, []byte{'n', 0xdc, 0x14, 0x3c}) + machine.I2C0.WriteRegister(0x09, 'n', []byte{0xdc, 0x14, 0x3c}) count = 1 case 1: // MediumPurple - machine.I2C0.WriteTo(0x09, []byte{'n', 0x93, 0x70, 0xdb}) + machine.I2C0.WriteRegister(0x09, 'n', []byte{0x93, 0x70, 0xdb}) count = 2 case 2: // MediumSeaGreen - machine.I2C0.WriteTo(0x09, []byte{'n', 0x3c, 0xb3, 0x71}) + machine.I2C0.WriteRegister(0x09, 'n', []byte{0x3c, 0xb3, 0x71}) count = 0 } diff --git a/src/machine/i2c.go b/src/machine/i2c.go index 9daa6ce0..5dc2cdd8 100644 --- a/src/machine/i2c.go +++ b/src/machine/i2c.go @@ -7,3 +7,26 @@ const ( TWI_FREQ_100KHZ = 100000 TWI_FREQ_400KHZ = 400000 ) + +// WriteRegister transmits first the register and then the data to the +// peripheral device. +// +// Many I2C-compatible devices are organized in terms of registers. This method +// is a shortcut to easily write to such registers. Also, it only works for +// devices with 7-bit addresses, which is the vast majority. +func (i2c I2C) WriteRegister(address uint8, register uint8, data []byte) error { + buf := make([]uint8, len(data)+1) + buf[0] = register + copy(buf[1:], data) + return i2c.Tx(uint16(address), buf, nil) +} + +// ReadRegister transmits the register, restarts the connection as a read +// operation, and reads the response. +// +// Many I2C-compatible devices are organized in terms of registers. This method +// is a shortcut to easily read such registers. Also, it only works for devices +// with 7-bit addresses, which is the vast majority. +func (i2c I2C) ReadRegister(address uint8, register uint8, data []byte) error { + return i2c.Tx(uint16(address), []byte{register}, data) +} diff --git a/src/machine/machine_avr.go b/src/machine/machine_avr.go index 7e18511c..dcced757 100644 --- a/src/machine/machine_avr.go +++ b/src/machine/machine_avr.go @@ -193,18 +193,48 @@ func (i2c I2C) Configure(config I2CConfig) { *avr.TWCR = avr.TWCR_TWEN } -// Start starts an I2C communication session. -func (i2c I2C) Start() { +// 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 { + if len(w) != 0 { + i2c.start(uint8(addr), true) // start transmission for writing + for _, b := range w { + i2c.writeByte(b) + } + } + if len(r) != 0 { + i2c.start(uint8(addr), false) // re-start transmission for reading + for i := range r { // read each char + r[i] = i2c.readByte() + } + } + if len(w) != 0 || len(r) != 0 { + // Stop the transmission after it has been started. + i2c.stop() + } + return nil +} + +// start starts an I2C communication session. +func (i2c I2C) start(address uint8, write bool) { // Clear TWI interrupt flag, put start condition on SDA, and enable TWI. *avr.TWCR = (avr.TWCR_TWINT | avr.TWCR_TWSTA | avr.TWCR_TWEN) // Wait till start condition is transmitted. for (*avr.TWCR & avr.TWCR_TWINT) == 0 { } + + // Write 7-bit shifted peripheral address. + address <<= 1 + if !write { + address |= 1 // set read flag + } + i2c.writeByte(address) } -// Stop ends an I2C communication session. -func (i2c I2C) Stop() { +// stop ends an I2C communication session. +func (i2c I2C) stop() { // Send stop condition. *avr.TWCR = (avr.TWCR_TWEN | avr.TWCR_TWINT | avr.TWCR_TWSTO) @@ -213,36 +243,8 @@ func (i2c I2C) Stop() { } } -// WriteTo writes a slice of data bytes to a peripheral with a specific address. -func (i2c I2C) WriteTo(address uint8, data []byte) { - i2c.Start() - - // Write 7-bit shifted peripheral address plus write flag(0) - i2c.WriteByte(address << 1) - - for _, v := range data { - i2c.WriteByte(v) - } - - i2c.Stop() -} - -// ReadFrom reads a slice of data bytes from an I2C peripheral with a specific address. -func (i2c I2C) ReadFrom(address uint8, data []byte) { - i2c.Start() - - // Write 7-bit shifted peripheral address + read flag(1) - i2c.WriteByte(address<<1 + 1) - - for i, _ := range data { - data[i] = i2c.ReadByte() - } - - i2c.Stop() -} - -// WriteByte writes a single byte to the I2C bus. -func (i2c I2C) WriteByte(data byte) { +// writeByte writes a single byte to the I2C bus. +func (i2c I2C) writeByte(data byte) { // Write data to register. *avr.TWDR = avr.RegValue(data) @@ -254,8 +256,8 @@ func (i2c I2C) WriteByte(data byte) { } } -// ReadByte reads a single byte from the I2C bus. -func (i2c I2C) ReadByte() byte { +// readByte reads a single byte from the I2C bus. +func (i2c I2C) readByte() byte { // Clear TWI interrupt flag and enable TWI. *avr.TWCR = (avr.TWCR_TWEN | avr.TWCR_TWINT | avr.TWCR_TWEA) diff --git a/src/machine/machine_nrf.go b/src/machine/machine_nrf.go index 8af896eb..eaa7ab6f 100644 --- a/src/machine/machine_nrf.go +++ b/src/machine/machine_nrf.go @@ -167,38 +167,68 @@ func (i2c I2C) Configure(config I2CConfig) { i2c.setPins(config.SCL, config.SDA) } -// WriteTo writes a slice of data bytes to a peripheral with a specific address. -func (i2c I2C) WriteTo(address uint8, data []byte) { - i2c.Bus.ADDRESS = nrf.RegValue(address) - i2c.Bus.TASKS_STARTTX = 1 - for _, v := range data { - i2c.Bus.TXD = nrf.RegValue(v) - for i2c.Bus.EVENTS_TXDSENT == 0 { +// 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 { + i2c.Bus.ADDRESS = nrf.RegValue(addr) + if len(w) != 0 { + i2c.Bus.TASKS_STARTTX = 1 // start transmission for writing + for _, b := range w { + i2c.writeByte(b) } - i2c.Bus.EVENTS_TXDSENT = 0 } + if len(r) != 0 { + i2c.Bus.TASKS_STARTRX = 1 // re-start transmission for reading + for i := range r { // read each char + if i+1 == len(r) { + // The 'stop' signal must be sent before reading back the last + // byte, so that it will be sent by the I2C peripheral right + // after the last byte has been read. + r[i] = i2c.readLastByte() + } else { + r[i] = i2c.readByte() + } + } + } else { + // Nothing to read back. Stop the transmission. + i2c.signalStop() + } + return nil +} - // Assume stop after write. +// signalStop sends a stop signal when writing or tells the I2C peripheral that +// it must generate a stop condition after the next character is retrieved when +// reading. +func (i2c I2C) signalStop() { i2c.Bus.TASKS_STOP = 1 for i2c.Bus.EVENTS_STOPPED == 0 { } i2c.Bus.EVENTS_STOPPED = 0 } -// ReadFrom reads a slice of data bytes from an I2C peripheral with a specific address. -func (i2c I2C) ReadFrom(address uint8, data []byte) { - i2c.Bus.ADDRESS = nrf.RegValue(address) - i2c.Bus.TASKS_STARTRX = 1 - for i, _ := range data { - for i2c.Bus.EVENTS_RXDREADY == 0 { - } - i2c.Bus.EVENTS_RXDREADY = 0 - data[i] = byte(i2c.Bus.RXD) +// writeByte writes a single byte to the I2C bus. +func (i2c I2C) writeByte(data byte) { + i2c.Bus.TXD = nrf.RegValue(data) + for i2c.Bus.EVENTS_TXDSENT == 0 { } - - // Assume stop after read. - i2c.Bus.TASKS_STOP = 1 - for i2c.Bus.EVENTS_STOPPED == 0 { - } - i2c.Bus.EVENTS_STOPPED = 0 + i2c.Bus.EVENTS_TXDSENT = 0 +} + +// readByte reads a single byte from the I2C bus. +func (i2c I2C) readByte() byte { + for i2c.Bus.EVENTS_RXDREADY == 0 { + } + i2c.Bus.EVENTS_RXDREADY = 0 + return byte(i2c.Bus.RXD) +} + +// readLastByte reads a single byte from the I2C bus, sending a stop signal +// after it has been read. +func (i2c I2C) readLastByte() byte { + for i2c.Bus.EVENTS_RXDREADY == 0 { + } + i2c.Bus.EVENTS_RXDREADY = 0 + i2c.signalStop() // signal 'stop' now, so it is sent when reading RXD + return byte(i2c.Bus.RXD) }