gobot.io/x/gobot/v2@v2.1.0/platforms/beaglebone/beaglebone_adaptor.go (about) 1 package beaglebone 2 3 import ( 4 "fmt" 5 "os" 6 "strconv" 7 "strings" 8 "sync" 9 10 multierror "github.com/hashicorp/go-multierror" 11 "gobot.io/x/gobot/v2" 12 "gobot.io/x/gobot/v2/platforms/adaptors" 13 "gobot.io/x/gobot/v2/system" 14 ) 15 16 type pwmPinData struct { 17 channel int 18 dir string 19 dirRegexp string 20 } 21 22 const ( 23 pwmPeriodDefault = 500000 // 0.5 ms = 2 kHz 24 25 defaultI2cBusNumber = 2 26 27 defaultSpiBusNumber = 0 28 defaultSpiChipNumber = 0 29 defaultSpiMode = 0 30 defaultSpiBitsNumber = 8 31 defaultSpiMaxSpeed = 500000 32 ) 33 34 // Adaptor is the gobot.Adaptor representation for the Beaglebone Black/Green 35 type Adaptor struct { 36 name string 37 sys *system.Accesser 38 mutex sync.Mutex 39 *adaptors.DigitalPinsAdaptor 40 *adaptors.PWMPinsAdaptor 41 *adaptors.I2cBusAdaptor 42 *adaptors.SpiBusAdaptor 43 usrLed string 44 analogPath string 45 pinMap map[string]int 46 pwmPinMap map[string]pwmPinData 47 analogPinMap map[string]string 48 } 49 50 // NewAdaptor returns a new Beaglebone Black/Green Adaptor 51 // 52 // Optional parameters: 53 // 54 // adaptors.WithGpiodAccess(): use character device gpiod driver instead of sysfs 55 // adaptors.WithSpiGpioAccess(sclk, nss, mosi, miso): use GPIO's instead of /dev/spidev#.# 56 func NewAdaptor(opts ...func(adaptors.Optioner)) *Adaptor { 57 sys := system.NewAccesser() 58 c := &Adaptor{ 59 name: gobot.DefaultName("BeagleboneBlack"), 60 sys: sys, 61 pinMap: bbbPinMap, 62 pwmPinMap: bbbPwmPinMap, 63 analogPinMap: bbbAnalogPinMap, 64 usrLed: "/sys/class/leds/beaglebone:green:", 65 analogPath: "/sys/bus/iio/devices/iio:device0", 66 } 67 68 c.DigitalPinsAdaptor = adaptors.NewDigitalPinsAdaptor(sys, c.translateAndMuxDigitalPin, opts...) 69 c.PWMPinsAdaptor = adaptors.NewPWMPinsAdaptor(sys, c.translateAndMuxPWMPin, 70 adaptors.WithPWMPinDefaultPeriod(pwmPeriodDefault)) 71 c.I2cBusAdaptor = adaptors.NewI2cBusAdaptor(sys, c.validateI2cBusNumber, defaultI2cBusNumber) 72 c.SpiBusAdaptor = adaptors.NewSpiBusAdaptor(sys, c.validateSpiBusNumber, defaultSpiBusNumber, defaultSpiChipNumber, 73 defaultSpiMode, defaultSpiBitsNumber, defaultSpiMaxSpeed) 74 return c 75 } 76 77 // Name returns the Adaptor name 78 func (c *Adaptor) Name() string { return c.name } 79 80 // SetName sets the Adaptor name 81 func (c *Adaptor) SetName(n string) { c.name = n } 82 83 // Connect create new connection to board and pins. 84 func (c *Adaptor) Connect() error { 85 c.mutex.Lock() 86 defer c.mutex.Unlock() 87 88 if err := c.SpiBusAdaptor.Connect(); err != nil { 89 return err 90 } 91 92 if err := c.I2cBusAdaptor.Connect(); err != nil { 93 return err 94 } 95 96 if err := c.PWMPinsAdaptor.Connect(); err != nil { 97 return err 98 } 99 return c.DigitalPinsAdaptor.Connect() 100 } 101 102 // Finalize releases all i2c devices and exported analog, digital, pwm pins. 103 func (c *Adaptor) Finalize() error { 104 c.mutex.Lock() 105 defer c.mutex.Unlock() 106 107 err := c.DigitalPinsAdaptor.Finalize() 108 109 if e := c.PWMPinsAdaptor.Finalize(); e != nil { 110 err = multierror.Append(err, e) 111 } 112 113 if e := c.I2cBusAdaptor.Finalize(); e != nil { 114 err = multierror.Append(err, e) 115 } 116 117 if e := c.SpiBusAdaptor.Finalize(); e != nil { 118 err = multierror.Append(err, e) 119 } 120 return err 121 } 122 123 // DigitalWrite writes a digital value to specified pin. 124 // valid usr pin values are usr0, usr1, usr2 and usr3 125 func (c *Adaptor) DigitalWrite(id string, val byte) error { 126 c.mutex.Lock() 127 defer c.mutex.Unlock() 128 129 if strings.Contains(id, "usr") { 130 fi, e := c.sys.OpenFile(c.usrLed+id+"/brightness", os.O_WRONLY|os.O_APPEND, 0666) 131 defer fi.Close() //nolint:staticcheck // for historical reasons 132 if e != nil { 133 return e 134 } 135 _, err := fi.WriteString(strconv.Itoa(int(val))) 136 return err 137 } 138 139 return c.DigitalPinsAdaptor.DigitalWrite(id, val) 140 } 141 142 // AnalogRead returns an analog value from specified pin 143 func (c *Adaptor) AnalogRead(pin string) (val int, err error) { 144 analogPin, err := c.translateAnalogPin(pin) 145 if err != nil { 146 return 147 } 148 fi, err := c.sys.OpenFile(fmt.Sprintf("%v/%v", c.analogPath, analogPin), os.O_RDONLY, 0644) 149 defer fi.Close() //nolint:staticcheck // for historical reasons 150 151 if err != nil { 152 return 153 } 154 155 var buf = make([]byte, 1024) 156 _, err = fi.Read(buf) 157 if err != nil { 158 return 159 } 160 161 val, _ = strconv.Atoi(strings.Split(string(buf), "\n")[0]) 162 return 163 } 164 165 func (c *Adaptor) validateSpiBusNumber(busNr int) error { 166 // Valid bus numbers are [0,1] which corresponds to /dev/spidev0.x through /dev/spidev1.x. 167 // x is the chip number <255 168 if (busNr < 0) || (busNr > 1) { 169 return fmt.Errorf("Bus number %d out of range", busNr) 170 } 171 return nil 172 } 173 174 func (c *Adaptor) validateI2cBusNumber(busNr int) error { 175 // Valid bus number is either 0 or 2 which corresponds to /dev/i2c-0 or /dev/i2c-2. 176 if (busNr != 0) && (busNr != 2) { 177 return fmt.Errorf("Bus number %d out of range", busNr) 178 } 179 return nil 180 } 181 182 // translateAnalogPin converts analog pin name to pin position 183 func (c *Adaptor) translateAnalogPin(pin string) (string, error) { 184 if val, ok := c.analogPinMap[pin]; ok { 185 return val, nil 186 } 187 return "", fmt.Errorf("Not a valid analog pin") 188 } 189 190 // translatePin converts digital pin name to pin position 191 func (c *Adaptor) translateAndMuxDigitalPin(id string) (string, int, error) { 192 line, ok := c.pinMap[id] 193 if !ok { 194 return "", -1, fmt.Errorf("'%s' is not a valid id for a digital pin", id) 195 } 196 // mux is done by id, not by line 197 if err := c.muxPin(id, "gpio"); err != nil { 198 return "", -1, err 199 } 200 return "", line, nil 201 } 202 203 func (c *Adaptor) translateAndMuxPWMPin(id string) (string, int, error) { 204 pinInfo, ok := c.pwmPinMap[id] 205 if !ok { 206 return "", -1, fmt.Errorf("'%s' is not a valid id for a PWM pin", id) 207 } 208 209 path, err := pinInfo.findPWMDir(c.sys) 210 if err != nil { 211 return "", -1, err 212 } 213 214 if err := c.muxPin(id, "pwm"); err != nil { 215 return "", -1, err 216 } 217 218 return path, pinInfo.channel, nil 219 } 220 221 func (p pwmPinData) findPWMDir(sys *system.Accesser) (dir string, err error) { 222 items, _ := sys.Find(p.dir, p.dirRegexp) 223 if len(items) == 0 { 224 return "", fmt.Errorf("No path found for PWM directory pattern, '%s' in path '%s'", p.dirRegexp, p.dir) 225 } 226 227 dir = items[0] 228 info, err := sys.Stat(dir) 229 if err != nil { 230 return "", fmt.Errorf("Error (%v) on access '%s'", err, dir) 231 } 232 if !info.IsDir() { 233 return "", fmt.Errorf("The item '%s' is not a directory, which is not expected", dir) 234 } 235 236 return 237 } 238 239 func (c *Adaptor) muxPin(pin, cmd string) error { 240 path := fmt.Sprintf("/sys/devices/platform/ocp/ocp:%s_pinmux/state", pin) 241 fi, e := c.sys.OpenFile(path, os.O_WRONLY, 0666) 242 defer fi.Close() //nolint:staticcheck // for historical reasons 243 if e != nil { 244 return e 245 } 246 _, e = fi.WriteString(cmd) 247 return e 248 }