gobot.io/x/gobot/v2@v2.1.0/platforms/tinkerboard/adaptor.go (about) 1 package tinkerboard 2 3 import ( 4 "fmt" 5 "sync" 6 7 multierror "github.com/hashicorp/go-multierror" 8 "gobot.io/x/gobot/v2" 9 "gobot.io/x/gobot/v2/platforms/adaptors" 10 "gobot.io/x/gobot/v2/system" 11 ) 12 13 const ( 14 debug = false 15 16 pwmInvertedIdentifier = "inversed" 17 18 defaultI2cBusNumber = 1 19 20 defaultSpiBusNumber = 0 21 defaultSpiChipNumber = 0 22 defaultSpiMode = 0 23 defaultSpiBitsNumber = 8 24 defaultSpiMaxSpeed = 500000 25 ) 26 27 type pwmPinDefinition struct { 28 channel int 29 dir string 30 dirRegexp string 31 } 32 33 // Adaptor represents a Gobot Adaptor for the ASUS Tinker Board 34 type Adaptor struct { 35 name string 36 sys *system.Accesser 37 mutex sync.Mutex 38 *adaptors.DigitalPinsAdaptor 39 *adaptors.PWMPinsAdaptor 40 *adaptors.I2cBusAdaptor 41 *adaptors.SpiBusAdaptor 42 } 43 44 // NewAdaptor creates a Tinkerboard Adaptor 45 // 46 // Optional parameters: 47 // 48 // adaptors.WithGpiodAccess(): use character device gpiod driver instead of sysfs (still used by default) 49 // adaptors.WithSpiGpioAccess(sclk, nss, mosi, miso): use GPIO's instead of /dev/spidev#.# 50 // adaptors.WithGpiosActiveLow(pin's): invert the pin behavior 51 // adaptors.WithGpiosPullUp/Down(pin's): sets the internal pull resistor 52 // 53 // note from RK3288 datasheet: "The pull direction (pullup or pulldown) for all of GPIOs are software-programmable", but 54 // the latter is not working for any pin (armbian 22.08.7) 55 func NewAdaptor(opts ...func(adaptors.Optioner)) *Adaptor { 56 sys := system.NewAccesser(system.WithDigitalPinGpiodAccess()) 57 c := &Adaptor{ 58 name: gobot.DefaultName("Tinker Board"), 59 sys: sys, 60 } 61 c.DigitalPinsAdaptor = adaptors.NewDigitalPinsAdaptor(sys, c.translateDigitalPin, opts...) 62 c.PWMPinsAdaptor = adaptors.NewPWMPinsAdaptor(sys, c.translatePWMPin, 63 adaptors.WithPolarityInvertedIdentifier(pwmInvertedIdentifier)) 64 c.I2cBusAdaptor = adaptors.NewI2cBusAdaptor(sys, c.validateI2cBusNumber, defaultI2cBusNumber) 65 c.SpiBusAdaptor = adaptors.NewSpiBusAdaptor(sys, c.validateSpiBusNumber, defaultSpiBusNumber, defaultSpiChipNumber, 66 defaultSpiMode, defaultSpiBitsNumber, defaultSpiMaxSpeed) 67 return c 68 } 69 70 // Name returns the name of the Adaptor 71 func (c *Adaptor) Name() string { return c.name } 72 73 // SetName sets the name of the Adaptor 74 func (c *Adaptor) SetName(n string) { c.name = n } 75 76 // Connect create new connection to board and pins. 77 func (c *Adaptor) Connect() error { 78 c.mutex.Lock() 79 defer c.mutex.Unlock() 80 81 if err := c.SpiBusAdaptor.Connect(); err != nil { 82 return err 83 } 84 85 if err := c.I2cBusAdaptor.Connect(); err != nil { 86 return err 87 } 88 89 if err := c.PWMPinsAdaptor.Connect(); err != nil { 90 return err 91 } 92 return c.DigitalPinsAdaptor.Connect() 93 } 94 95 // Finalize closes connection to board, pins and bus 96 func (c *Adaptor) Finalize() error { 97 c.mutex.Lock() 98 defer c.mutex.Unlock() 99 100 err := c.DigitalPinsAdaptor.Finalize() 101 102 if e := c.PWMPinsAdaptor.Finalize(); e != nil { 103 err = multierror.Append(err, e) 104 } 105 106 if e := c.I2cBusAdaptor.Finalize(); e != nil { 107 err = multierror.Append(err, e) 108 } 109 110 if e := c.SpiBusAdaptor.Finalize(); e != nil { 111 err = multierror.Append(err, e) 112 } 113 return err 114 } 115 116 func (c *Adaptor) validateSpiBusNumber(busNr int) error { 117 // Valid bus numbers are [0,2] which corresponds to /dev/spidev0.x, /dev/spidev2.x 118 // x is the chip number <255 119 if (busNr != 0) && (busNr != 2) { 120 return fmt.Errorf("Bus number %d out of range", busNr) 121 } 122 return nil 123 } 124 125 func (c *Adaptor) validateI2cBusNumber(busNr int) error { 126 // Valid bus number is [0..4] which corresponds to /dev/i2c-0 through /dev/i2c-4. 127 // We don't support "/dev/i2c-6 DesignWare HDMI". 128 if (busNr < 0) || (busNr > 4) { 129 return fmt.Errorf("Bus number %d out of range", busNr) 130 } 131 return nil 132 } 133 134 func (c *Adaptor) translateDigitalPin(id string) (string, int, error) { 135 pindef, ok := gpioPinDefinitions[id] 136 if !ok { 137 return "", -1, fmt.Errorf("'%s' is not a valid id for a digital pin", id) 138 } 139 if c.sys.IsSysfsDigitalPinAccess() { 140 return "", pindef.sysfs, nil 141 } 142 chip := fmt.Sprintf("gpiochip%d", pindef.cdev.chip) 143 line := int(pindef.cdev.line) 144 return chip, line, nil 145 } 146 147 func (c *Adaptor) translatePWMPin(id string) (string, int, error) { 148 pinInfo, ok := pwmPinDefinitions[id] 149 if !ok { 150 return "", -1, fmt.Errorf("'%s' is not a valid id for a PWM pin", id) 151 } 152 path, err := pinInfo.findPWMDir(c.sys) 153 if err != nil { 154 return "", -1, err 155 } 156 return path, pinInfo.channel, nil 157 } 158 159 func (p pwmPinDefinition) findPWMDir(sys *system.Accesser) (dir string, err error) { 160 items, _ := sys.Find(p.dir, p.dirRegexp) 161 if len(items) == 0 { 162 return "", fmt.Errorf("No path found for PWM directory pattern, '%s' in path '%s'. See README.md for activation", 163 p.dirRegexp, p.dir) 164 } 165 166 dir = items[0] 167 info, err := sys.Stat(dir) 168 if err != nil { 169 return "", fmt.Errorf("Error (%v) on access '%s'", err, dir) 170 } 171 if !info.IsDir() { 172 return "", fmt.Errorf("The item '%s' is not a directory, which is not expected", dir) 173 } 174 175 return 176 }