diff --git a/Makefile b/Makefile index 11c8998..0dfa581 100644 --- a/Makefile +++ b/Makefile @@ -14,6 +14,8 @@ smoke-test: 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 + tinygo build -size short -o ./build/test.elf -target=bluepill ./examples/ds1307/sram/main.go + tinygo build -size short -o ./build/test.elf -target=bluepill ./examples/ds1307/time/main.go tinygo build -size short -o ./build/test.elf -target=itsybitsy-m0 ./examples/ds3231/main.go tinygo build -size short -o ./build/test.elf -target=microbit ./examples/easystepper/main.go tinygo build -size short -o ./build/test.elf -target=itsybitsy-m0 ./examples/espat/espconsole/main.go diff --git a/README.md b/README.md index 13180d2..0d8692a 100644 --- a/README.md +++ b/README.md @@ -59,6 +59,7 @@ func main() { | [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 | +| [DS1307 real time clock](https://datasheets.maximintegrated.com/en/ds/DS1307.pdf) | I2C | | [DS3231 real time clock](https://datasheets.maximintegrated.com/en/ds/DS3231.pdf) | I2C | | ["Easystepper" stepper motor controller](https://en.wikipedia.org/wiki/Stepper_motor) | GPIO | | [ESP8266/ESP32 AT Command set for WiFi/TCP/UDP](https://github.com/espressif/esp32-at) | UART | diff --git a/ds1307/ds1307.go b/ds1307/ds1307.go new file mode 100644 index 0000000..1fa8dcd --- /dev/null +++ b/ds1307/ds1307.go @@ -0,0 +1,168 @@ +package ds1307 + +import ( + "errors" + "time" + + "machine" +) + +// Device wraps an I2C connection to a DS1307 device. +type Device struct { + bus machine.I2C + Address uint8 + AddressSRAM uint8 +} + +// New creates a new DS1307 connection. I2C bus must be already configured. +func New(bus machine.I2C) Device { + return Device{bus: bus, + Address: uint8(I2CAddress), + AddressSRAM: SRAMBeginAddres, + } +} + +// SetTime sets the time and date +func (d *Device) SetTime(t time.Time) error { + data := make([]byte, 8) + data[0] = uint8(TimeDate) + data[1] = decToBcd(t.Second()) + data[2] = decToBcd(t.Minute()) + data[3] = decToBcd(t.Hour()) + data[4] = decToBcd(int(t.Weekday() + 1)) + data[5] = decToBcd(t.Day()) + data[6] = decToBcd(int(t.Month())) + data[7] = decToBcd(t.Year() - 2000) + err := d.bus.Tx(uint16(d.Address), data, nil) + return err +} + +// Time returns the time and date +func (d *Device) Time() (time.Time, error) { + data := make([]byte, 8) + err := d.bus.ReadRegister(d.Address, uint8(TimeDate), data) + if err != nil { + return time.Time{}, err + } + seconds := bcdToDec(data[0] & 0x7F) + minute := bcdToDec(data[1]) + hour := hoursBCDToInt(data[2]) + day := bcdToDec(data[4]) + month := time.Month(bcdToDec(data[5])) + year := bcdToDec(data[6]) + year += 2000 + + t := time.Date(year, month, day, hour, minute, seconds, 0, time.UTC) + return t, nil +} + +// 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) { + switch whence { + case 0: + whence = SRAMBeginAddres + case 1: + whence = int(d.AddressSRAM) + case 2: + whence = SRAMEndAddress + default: + return 0, errors.New("Invalid starting point") + } + d.AddressSRAM = uint8(whence) + uint8(offset) + if d.AddressSRAM > SRAMEndAddress { + return 0, errors.New("EOF") + } + return int64(d.AddressSRAM), 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) { + if int(d.AddressSRAM)+len(data)-1 > SRAMEndAddress { + return 0, errors.New("Writing outside of SRAM") + } + buffer := make([]byte, len(data)+1) + buffer[0] = d.AddressSRAM + copy(buffer[1:], data) + err = d.bus.Tx(uint16(d.Address), buffer, nil) + if err != nil { + return 0, err + } + d.Seek(int64(len(data)), 1) + return len(data), nil +} + +// 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) { + if int(d.AddressSRAM)+len(data)-1 > SRAMEndAddress { + return 0, errors.New("EOF") + } + err = d.bus.ReadRegister(d.Address, d.AddressSRAM, data) + if err != nil { + return 0, err + } + d.Seek(int64(len(data)), 1) + return len(data), nil +} + +// SetOscillatorFrequency sets output oscillator frequency +// Available modes: SQW_OFF, SQW_1HZ, SQW_4KHZ, SQW_8KHZ, SQW_32KHZ +func (d *Device) SetOscillatorFrequency(sqw uint8) error { + data := []byte{uint8(Control), sqw} + err := d.bus.Tx(uint16(d.Address), data, nil) + return err +} + +// IsOscillatorRunning returns if the oscillator is running +func (d *Device) IsOscillatorRunning() bool { + data := []byte{0} + err := d.bus.ReadRegister(d.Address, uint8(TimeDate), data) + if err != nil { + return false + } + return (data[0] & (1 << CH)) == 0 +} + +// SetOscillatorRunning starts/stops internal oscillator by toggling halt bit +func (d *Device) SetOscillatorRunning(running bool) error { + data := make([]byte, 3) + err := d.bus.ReadRegister(d.Address, uint8(TimeDate), data) + if err != nil { + return err + } + if running { + data[0] &^= (1 << CH) + } else { + data[0] |= (1 << CH) + } + data[1], data[0] = data[0], uint8(TimeDate) + err = d.bus.Tx(uint16(d.Address), data[:2], nil) + return err +} + +// decToBcd converts int to BCD +func decToBcd(dec int) uint8 { + return uint8(dec + 6*(dec/10)) +} + +// bcdToDec converts BCD to int +func bcdToDec(bcd uint8) int { + return int(bcd - 6*(bcd>>4)) +} + +// hoursBCDToInt converts the BCD hours to int +func hoursBCDToInt(value uint8) (hour int) { + if value&0x40 != 0x00 { + hour = bcdToDec(value & 0x1F) + if (value & 0x20) != 0x00 { + hour += 12 + } + } else { + hour = bcdToDec(value) + } + return +} diff --git a/ds1307/registers.go b/ds1307/registers.go new file mode 100644 index 0000000..d245318 --- /dev/null +++ b/ds1307/registers.go @@ -0,0 +1,19 @@ +package ds1307 + +const ( + I2CAddress = 0x68 + TimeDate = 0x00 + Control = 0x7 + //CH is oscillator halt bit + CH = 0x7 + SRAMBeginAddres = 0x8 + SRAMEndAddress = 0x3F +) + +const ( + SQW_OFF = 0x0 + SQW_1HZ = 0x10 + SQW_4KHZ = 0x11 + SQW_8KHZ = 0x12 + SQW_32KHZ = 0x13 +) diff --git a/examples/ds1307/sram/main.go b/examples/ds1307/sram/main.go new file mode 100644 index 0000000..683509b --- /dev/null +++ b/examples/ds1307/sram/main.go @@ -0,0 +1,32 @@ +package main + +import ( + "machine" + + "github.com/tinygo-org/drivers/ds1307" +) + +func main() { + machine.I2C0.Configure(machine.I2CConfig{}) + rtc := ds1307.New(machine.I2C0) + read := make([]byte, 5) + for { + rtc.Seek(0, 0) + _, err := rtc.Write([]byte{1, 2, 3, 4, 5}) + if err != nil { + println("Error while writing data:", err) + break + } + rtc.Seek(0, 0) + _, err = rtc.Read(read) + if err != nil { + println("Error while reading data:", err) + break + } + for data := range read { + println(data, " ") + } + + } + +} diff --git a/examples/ds1307/time/main.go b/examples/ds1307/time/main.go new file mode 100644 index 0000000..3fbde50 --- /dev/null +++ b/examples/ds1307/time/main.go @@ -0,0 +1,25 @@ +package main + +import ( + "machine" + "time" + + "github.com/tinygo-org/drivers/ds1307" +) + +func main() { + machine.I2C0.Configure(machine.I2CConfig{}) + rtc := ds1307.New(machine.I2C0) + rtc.SetTime(time.Date(2019, 5, 15, 20, 34, 12, 0, time.UTC)) + + for { + t, err := rtc.Time() + if err != nil { + println("Error reading date:", err) + break + } + println(t.Hour(), ":", t.Minute(), ":", t.Second(), " ", t.Day(), "/", t.Month(), "/", t.Year()) + + } + +}