diff --git a/nxp74hc595/74hc595.go b/nxp74hc595/74hc595.go new file mode 100644 index 0000000..2755330 --- /dev/null +++ b/nxp74hc595/74hc595.go @@ -0,0 +1,190 @@ +// Copyright 2025 The Periph Authors. All rights reserved. +// Use of this source code is governed under the Apache License, Version 2.0 +// that can be found in the LICENSE file. + +// The 74HC595 is a serial shift register. It converts a serial stream to a +// parallel output. For example, you can use it as an SPI => Parallel +// converter. +// +// # Datasheet +// +// https://www.nexperia.com/product/74HC595D +// +// There's a nice tutorial on the device here: +// +// https://docs.arduino.cc/tutorials/communication/guide-to-shift-out/ +package nxp74hc595 + +import ( + "errors" + "fmt" + "sync" + "time" + + "periph.io/x/conn/v3/gpio" + "periph.io/x/conn/v3/pin" + "periph.io/x/conn/v3/spi" +) + +const ( + devMask = 0xff + devName = "74HC595" + numPins = 8 +) + +var ( + ErrNotImplemented = errors.New("nxp74hc595: not implemented") +) + +// Dev represents a 74hc595 device. +type Dev struct { + Pins []gpio.PinOut + + mu sync.Mutex + conn spi.Conn + value gpio.GPIOValue +} + +// Group implements gpio.Group and provides a way to write to multiple GPO pins +// in a single transaction. +type Group struct { + dev *Dev + pins []Pin +} + +// New accepts an spi.Conn and returns a new HC74595 device. +func New(conn spi.Conn) (*Dev, error) { + // setting value to an invalid initial state forces the first write to + // happen, even if it's 0. + dev := Dev{conn: conn, value: gpio.GPIOValue(1 << 9), Pins: make([]gpio.PinOut, numPins)} + for ix := range numPins { + dev.Pins[ix] = &Pin{number: ix, name: fmt.Sprintf("%s_GPO%d", devName, ix), dev: &dev} + } + return &dev, nil +} + +// write does the low-level write to the device. +func (dev *Dev) write(value, mask gpio.GPIOValue) error { + + dev.mu.Lock() + defer dev.mu.Unlock() + newValue := (dev.value & (devMask ^ mask)) | (value & mask) + if dev.value == newValue { + return nil + } + var err error + var w = []byte{byte(newValue)} + err = dev.conn.Tx(w, nil) + if err == nil { + dev.value = newValue + } + return err +} + +// Group returns a subset of pins on the device as a gpio.Group. A Group +// allows you to write to multiple pins in a single transaction. +func (dev *Dev) Group(pins ...int) (gpio.Group, error) { + gr := Group{dev: dev, pins: make([]Pin, len(pins))} + for ix, pinNumber := range pins { + if p, ok := dev.Pins[pinNumber].(*Pin); ok { + gr.pins[ix] = *p + } + } + return &gr, nil +} + +// Halt disables the device +func (dev *Dev) Halt() (err error) { + dev.mu.Lock() + defer dev.mu.Unlock() + dev.Pins = make([]gpio.PinOut, 0) + dev.conn = nil + return +} + +func (dev *Dev) String() string { + return devName +} + +// Return the set of GPO Pins that are associated with this group. +func (gr *Group) Pins() []pin.Pin { + result := make([]pin.Pin, len(gr.pins)) + for ix, p := range gr.pins { + result[ix] = &p + } + return result + +} + +// Given an offset of a pin into the group, return that pin. +func (gr *Group) ByOffset(offset int) pin.Pin { + return &gr.pins[offset] +} + +// Given a name of a pin in the group, return that pin. +func (gr *Group) ByName(name string) pin.Pin { + for _, p := range gr.pins { + if p.name == name { + return &p + } + } + return nil +} + +// Given the pin number of a pin within the group, return that pin. +func (gr *Group) ByNumber(number int) pin.Pin { + for _, p := range gr.pins { + if p.number == number { + return &p + } + } + return nil +} + +// Out writes the value to the device. Only pins identified by mask are +// modified. +func (gr *Group) Out(value, mask gpio.GPIOValue) error { + if mask == 0 { + mask = gpio.GPIOValue(1<