diff --git a/Makefile b/Makefile index dd9cafc..c052910 100644 --- a/Makefile +++ b/Makefile @@ -11,6 +11,7 @@ smoke-test: @mkdir -p build tinygo build -size short -o ./build/test.elf -target=itsybitsy-m0 ./examples/adxl345/main.go tinygo build -size short -o ./build/test.elf -target=itsybitsy-m0 ./examples/apa102/main.go + tinygo build -size short -o ./build/test.elf -target=microbit ./examples/at24cx/main.go tinygo build -size short -o ./build/test.elf -target=itsybitsy-m0 ./examples/bh1750/main.go tinygo build -size short -o ./build/test.elf -target=itsybitsy-m0 ./examples/blinkm/main.go tinygo build -size short -o ./build/test.elf -target=itsybitsy-m0 ./examples/bmp180/main.go diff --git a/README.md b/README.md index 7624842..712fa59 100644 --- a/README.md +++ b/README.md @@ -56,6 +56,7 @@ func main() { |----------|-------------| | [ADXL345 accelerometer](http://www.analog.com/media/en/technical-documentation/data-sheets/ADXL345.pdf) | I2C | | [APA102 RGB LED](https://cdn-shop.adafruit.com/product-files/2343/APA102C.pdf) | SPI | +| [AT24CX 2-wire serial EEPROM](https://www.openimpulse.com/blog/wp-content/uploads/wpsc/downloadables/24C32-Datasheet.pdf) | I2C | | [BH1750 ambient light sensor](https://www.mouser.com/ds/2/348/bh1750fvi-e-186247.pdf) | I2C | | [BlinkM RGB LED](http://thingm.com/fileadmin/thingm/downloads/BlinkM_datasheet.pdf) | I2C | | [BMP180 barometer](https://cdn-shop.adafruit.com/datasheets/BST-BMP180-DS000-09.pdf) | I2C | diff --git a/at24cx/at24cx.go b/at24cx/at24cx.go new file mode 100644 index 0000000..bef58a6 --- /dev/null +++ b/at24cx/at24cx.go @@ -0,0 +1,171 @@ +// Package at24cx provides a driver for the AT24C32/64/128/256/512 2-wire serial EEPROM +// +// Datasheet: +// https://www.openimpulse.com/blog/wp-content/uploads/wpsc/downloadables/24C32-Datasheet.pdf +package at24cx // import "tinygo.org/x/drivers/at24cx" + +import ( + "errors" + "machine" + "time" +) + +// Device wraps an I2C connection to a DS3231 device. +type Device struct { + bus machine.I2C + Address uint16 + pageSize uint16 + currentRAMAddress uint16 + startRAMAddress uint16 + endRAMAddress uint16 +} + +type Config struct { + PageSize uint16 + StartRAMAddress uint16 + EndRAMAddress uint16 +} + +// New creates a new AT24C32/64 connection. The I2C bus must already be +// configured. +// +// This function only creates the Device object, it does not touch the device. +func New(bus machine.I2C) Device { + return Device{ + bus: bus, + Address: Address, + } +} + +// Configure sets up the device for communication +func (d *Device) Configure(cfg Config) { + if cfg.PageSize == 0 { + d.pageSize = 32 + } else { + d.pageSize = cfg.PageSize + } + if cfg.EndRAMAddress == 0 { + d.endRAMAddress = 4096 + } else { + d.endRAMAddress = cfg.EndRAMAddress + } + d.startRAMAddress = cfg.StartRAMAddress +} + +// WriteByte writes a byte at the specified address +func (d *Device) WriteByte(eepromAddress uint16, value uint8) error { + address := []uint8{ + uint8((eepromAddress >> 8) & 0xFF), + uint8(eepromAddress & 0xFF), + value, + } + return d.bus.Tx(d.Address, address, nil) +} + +// ReadByte reads the byte at the specified address +func (d *Device) ReadByte(eepromAddress uint16) (uint8, error) { + address := []uint8{ + uint8(eepromAddress >> 8), + uint8(eepromAddress & 0xFF), + } + data := make([]uint8, 1) + err := d.bus.Tx(d.Address, address, data) + return data[0], err +} + +// WriteAt writes a byte array at the specified address +func (d *Device) WriteAt(data []byte, offset int64) (n int, err error) { + return d.writeAt(data, uint16(offset)) +} + +// writeAt writes a byte array at the specified address +func (d *Device) writeAt(data []byte, offset uint16) (n int, err error) { + values := make([]uint8, 32) + dataLeft := uint16(len(data)) + d.currentRAMAddress = offset + offset = 0 + var offsetPage uint16 + var chunkLength uint16 + for dataLeft > 0 { + offsetPage = d.currentRAMAddress % d.pageSize + if dataLeft < 30 { // The 32K/64K EEPROM is capable of 32-byte page writes and we're using 2 for the address + chunkLength = dataLeft + } else { + chunkLength = 30 + } + if (d.pageSize - offsetPage) < chunkLength { + chunkLength = d.pageSize - offsetPage + } + for i := uint16(0); i < chunkLength; i++ { + values[2+i] = data[offset+i] + } + values[0] = uint8(d.currentRAMAddress >> 8) + values[1] = uint8(d.currentRAMAddress & 0xFF) + err := d.bus.Tx(d.Address, values[:chunkLength+2], nil) + if err != nil { + return 0, err + } + dataLeft -= chunkLength + offset += chunkLength + if d.endRAMAddress-chunkLength < d.currentRAMAddress { + d.currentRAMAddress = d.startRAMAddress + (d.currentRAMAddress+uint16(len(data)))%d.endRAMAddress + } else { + d.currentRAMAddress += chunkLength + } + time.Sleep(2 * time.Millisecond) // writing again too soon will block the device + } + return len(data), nil +} + +// ReadAt reads the bytes at the specified address +func (d *Device) ReadAt(data []byte, offset int64) (n int, err error) { + return d.readAt(data, uint16(offset)) +} + +// readAt reads the bytes at the specified address +func (d *Device) readAt(data []byte, offset uint16) (n int, err error) { + address := []uint8{ + uint8((offset >> 8) & 0xFF), + uint8(offset & 0xFF), + } + err = d.bus.Tx(d.Address, address, data) + + if d.endRAMAddress-uint16(len(data)) < offset { + d.currentRAMAddress = d.startRAMAddress + (offset+uint16(len(data)))%d.endRAMAddress + } else { + d.currentRAMAddress = offset + uint16(len(data)) + } + return len(data), err +} + +// Seek sets the offset for the next Read or Write on SRAM to offset, interpreted +// according to whence: 0 means relative to the origin of the SRAM, 1 means +// relative to the current offset, and 2 means relative to the end. +// returns new offset and error, if any +func (d *Device) Seek(offset int64, whence int) (int64, error) { + w := uint16(0) + switch whence { + case 0: + w = d.startRAMAddress + case 1: + w = d.currentRAMAddress + case 2: + w = d.endRAMAddress + default: + return 0, errors.New("invalid whence") + } + d.currentRAMAddress = w + uint16(offset) + return int64(d.currentRAMAddress), nil +} + +// Write writes len(data) bytes to SRAM +// returns number of bytes written and error, if any +func (d *Device) Write(data []byte) (n int, err error) { + return d.writeAt(data, d.currentRAMAddress) +} + +// Read reads len(data) from SRAM +// returns number of bytes written and error, if any +func (d *Device) Read(data []uint8) (n int, err error) { + return d.readAt(data, d.currentRAMAddress) +} diff --git a/at24cx/registers.go b/at24cx/registers.go new file mode 100644 index 0000000..f001fc4 --- /dev/null +++ b/at24cx/registers.go @@ -0,0 +1,4 @@ +package at24cx + +// The I2C address which this device listens to. +const Address = 0x57 diff --git a/examples/at24cx/main.go b/examples/at24cx/main.go new file mode 100644 index 0000000..d23f5d1 --- /dev/null +++ b/examples/at24cx/main.go @@ -0,0 +1,122 @@ +package main + +import ( + "machine" + "time" + + "tinygo.org/x/drivers/at24cx" +) + +func main() { + machine.I2C0.Configure(machine.I2CConfig{}) + + eeprom := at24cx.New(machine.I2C0) + eeprom.Configure(at24cx.Config{}) + + values := make([]uint8, 100) + for i := uint16(0); i < 100; i++ { + values[i] = uint8(65 + i%26) + } + _, err := eeprom.WriteAt(values, 0) + if err != nil { + println("There was an error in WriteAt:", err) + return + } + + for i := uint16(0); i < 26; i++ { + err = eeprom.WriteByte(100+i, uint8(90-i)) + if err != nil { + println("There was an error in WriteByte:", i, err) + return + } + time.Sleep(2 * time.Millisecond) + } + + println("\n\r\n\rRead 26 bytes one by one from address 0") + println("Expected: ABCDEFGHIJKLMNOPQRSTUVWXYZ") + print("Real: ") + for i := uint16(0); i < 26; i++ { + char, err := eeprom.ReadByte(i) + print(string(char)) + if err != nil { + println("There was an error in ReadByte:", i, err) + return + } + } + println("") + + println("\n\r\n\rRead 100 bytes from address 26") + println("Expected: ABCDEFGHIJKLMNOPQRSTUVWXYZABCDEFGHIJKLMNOPQRSTUVWXYZABCDEFGHIJKLMNOPQRSTUVZYXWVUTSRQPONMLKJIHGFEDCBA") + print("Real: ") + data := make([]byte, 100) + _, err = eeprom.ReadAt(data, 26) + if err != nil { + println("There was an error in ReadAt:", err) + return + } + for i := 0; i < 100; i++ { + print(string(data[i])) + } + println("") + + // Move to the beginning of memory + eeprom.Seek(0, 0) + _, err = eeprom.Write([]uint8{88, 88, 88}) + if err != nil { + println("There was an error in Write:", err) + return + } + + println("\n\r\n\rRead 3 bytes") + println("Expected: DEF") + print("Real: ") + data = make([]byte, 3) + _, err = eeprom.Read(data) + if err != nil { + println("There was an error in Read:", err) + return + } + for _, char := range data { + print(string(char)) + } + println("") + + println("\n\r\n\rRead another 3 bytes (from the beginning this time)") + eeprom.Seek(-6, 1) + println("Expected: XXX") + print("Real: ") + data = make([]byte, 3) + _, err = eeprom.Read(data) + if err != nil { + println("There was an error in Read:", err) + return + } + for _, char := range data { + print(string(char)) + } + println("") + + // Move to the end of memory + eeprom.Seek(-4, 2) + _, err = eeprom.Write([]uint8{89, 90, 89, 90}) + if err != nil { + println("There was an error in Write:", err) + return + } + + println("\n\r\n\rRead the last 4 bytes of the memory and the 3 of the beginning") + eeprom.Seek(-4, 1) + println("Expected: YZYZXXX") + print("Real: ") + data = make([]byte, 7) + _, err = eeprom.Read(data) + if err != nil { + println("There was an error in Read:", err) + return + } + for _, char := range data { + print(string(char)) + } + println("") + +}