├── .gitignore ├── LICENSE ├── README.md ├── cpuinfo.go ├── devices ├── bh1750fvi │ ├── README.md │ └── bh1750fvi.go ├── gy520 │ ├── README.md │ └── gy520.go ├── hd44780 │ ├── README.md │ └── hd44780_i2c.go ├── mcp23017 │ ├── README.md │ └── mcp23017.go ├── nunchuck │ ├── README.md │ └── nunchuck.go └── tmp102 │ ├── README.md │ └── tmp102.go ├── driver_beagle_black.go ├── driver_mock.go ├── driver_odroid_c1.go ├── driver_pi_dt.go ├── dt_pin_config.go ├── examples ├── blink.go ├── pinmap.go ├── shiftout.go └── tlc5940.go ├── hwio.go ├── hwio_test.go ├── module.go ├── module_bb_analog.go ├── module_bb_pwm.go ├── module_dtgpio.go ├── module_dti2c.go ├── module_dtleds.go ├── module_odroidc1_analog.go ├── module_preassigned.go ├── pin.go └── servo ├── README.md └── servo.go /.gitignore: -------------------------------------------------------------------------------- 1 | *~ 2 | .idea/ 3 | /examples/blink 4 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2014, Mark Stephens 2 | All rights reserved. 3 | 4 | Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: 5 | 6 | 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. 7 | 8 | 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. 9 | 10 | 3. Neither the name of the copyright holder nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission. 11 | 12 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 13 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # hwio 2 | 3 | ## Introduction 4 | 5 | hwio is a Go library for interfacing with hardware I/O, particularly on 6 | SoC-based boards such as BeagleBone Black, Raspberry Pi and Odroid-C1. It is 7 | loosely modelled on the Arduino programming style, but deviating where that doesn't make sense in Go. It makes use of a thin hardware abstraction via an 8 | interface so a program written against the library for say a BeagleBone could 9 | be easily compiled to run on a Raspberry Pi, maybe only changing pin 10 | references. 11 | 12 | To use hwio, you just need to import it into your Go project, initialise modules and pins as 13 | required, and then use functions that manipulate the pins. 14 | 15 | For more information about the library, including pin diagrams for supported boards and tutorials, 16 | see http://stuffwemade.net/hwio. 17 | 18 | ## Digital Reads and Writes (GPIO) 19 | 20 | Initialising a pin looks like this: 21 | 22 | myPin, err := hwio.GetPin("gpio4") 23 | err = hwio.PinMode(myPin, hwio.OUTPUT) 24 | 25 | Or the shorter, more convenient form: 26 | 27 | myPin, err := GetPinWithMode("gpio4", hwio.OUTPUT) 28 | 29 | Unlike Arduino, where the pins are directly numbered and you just use the number, in hwio 30 | you get the pin first, by name. This is necessary as different hardware drivers may provide 31 | different pins. 32 | 33 | The mode constants include: 34 | 35 | * INPUT - set pin to digital input 36 | * OUTPUT - set pin to digital output 37 | 38 | (Pull-ups and pull-downs are not currently supported by the drivers, as this is not apparently exposed to file system.) 39 | 40 | Writing a value to a pin looks like this: 41 | 42 | hwio.DigitalWrite(myPin, hwio.HIGH) 43 | 44 | Reading a value from a digital pin looks like this, returning a HIGH or LOW: 45 | 46 | value, err := hwio.DigitalRead(myPin) 47 | 48 | ## Analog 49 | 50 | Analog pins are available on BeagleBone Black. Unlike Arduino, before using analog pins you need to enable the module. 51 | This is because external programs may have them open. 52 | 53 | analog, e := hwio.GetAnalogModule() 54 | if e != nil { 55 | fmt.Printf("could not get analog module: %s\n", e) 56 | return 57 | } 58 | 59 | analog.Enable() 60 | 61 | Reading an analog value looks like this: 62 | 63 | value, err := hwio.AnalogRead(somePin) 64 | 65 | Analog values (on BeagleBone Black at least) are integers typically between 0-1800, which is the number of millivolts. 66 | (Note that you cannot drive analog inputs more than 1.8 volts on the BeagleBone, and you should use the analog voltage 67 | references it provides). 68 | 69 | (Note: the Raspberry Pi does not have analog inputs onboard, and is not covered by the analog functions of hwio. However it is possible to use i2c to read from a compatible device, such as the MCP4725 or ADS1015. Adafruit has breakout boards for these devices.) 70 | 71 | ## Cleaning Up on Exit 72 | 73 | At the end of your application, call CloseAll(). This can be done at the end of the main() function with a defer: 74 | 75 | defer hwio.CloseAll() 76 | 77 | This will ensure that resources allocated (particularly GPIO pins) will be released, even if there is a panic. 78 | 79 | If you want to close an individual GPIO pin, you can use: 80 | 81 | hwio.ClosePin(pin) 82 | 83 | ## Utility Functions 84 | 85 | To delay a number of milliseconds: 86 | 87 | hwio.Delay(500) // delay 500ms 88 | 89 | Or to delay by microseconds: 90 | 91 | hwio.DelayMicroseconds(1500) // delay 1500 usec, or 1.5 milliseconds 92 | 93 | The Arduino ShiftOut function is supported in a simplified form for 8 bits: 94 | 95 | e := hwio.ShiftOut(dataPin, clockPin, 127, hwio.MSBFIRST) // write 8 bits, MSB first 96 | 97 | or in a bigger variant that supports different sizes: 98 | 99 | e := hwio.ShiftOutSize(dataPin, clockPin, someValue, hwio.LSBFIRST, 12) // write 12 bits LSB first 100 | 101 | Sometimes you might want to write an unsigned int to a set of digital pins (e.g. a parallel port). This can be done as 102 | follows: 103 | 104 | somePins := []hwio.Pin{myPin3, myPin2, myPin1, myPin0} 105 | e := hwio.WriteUIntToPins(myValue, somePins) 106 | 107 | This will write out the n lowest bits of myValue, with the most significant bit of that value written to myPin3 etc. It uses DigitalWrite 108 | so the outputs are not written instantaneously. 109 | 110 | There is an implementation of the Arduino map() function: 111 | 112 | // map a value in range 0-1800 to new range 0-1023 113 | i := hwio.Map(value, 0, 1800, 0, 1023) 114 | 115 | To pulse a GPIO pin (must have been assigned), you can use the Pulse function: 116 | 117 | e := hwio.Pulse(somePin, hwio.HIGH, 1500) 118 | 119 | The second parameter is the logic level of the active level of the pulse. First the function sets the pin to 120 | the inactive state and then to the active state, before waiting the specified number of microseconds, and setting it inactive again. 121 | 122 | 123 | ## On-board LEDs 124 | 125 | On-board LEDs can be controlled using the helper function Led: 126 | 127 | // Turn on usr0 LED 128 | e := hwio.Led("usr0", true) 129 | 130 | For BeagleBone, the LEDs are named "usr0", "usr1", "usr2" and "usr3" (case-insensitive). For Raspberry Pi only one of the LEDs is controllable, 131 | which is named "OK". 132 | 133 | The Led function is a helper that uses the LED module. This provides more options to control what is displayed on each LED. 134 | 135 | ## I2C 136 | 137 | I2C is supported on BeagleBone Black and Raspberry Pi. It is accessible through the "i2c" module (BBB i2c2 pins), as follows: 138 | 139 | m, e := hwio.GetModule("i2c") 140 | if e != nil { 141 | fmt.Printf("could not get i2c module: %s\n", e) 142 | return 143 | } 144 | i2c := m.(hwio.I2CModule) 145 | 146 | // Uncomment on Raspberry pi, which doesn't automatically enable i2c bus. BeagleBone does, 147 | // as the default device tree enables it. 148 | 149 | // i2c.Enable() 150 | // defer i2c.Disable() 151 | 152 | device := i2c.GetDevice(0x68) 153 | 154 | Once you have a device, you can use Write, WriteBytes, Read or ReadBytes to set or get data from the i2c device. 155 | 156 | e.g. 157 | 158 | device.WriteByte(controlRegister, someValue) 159 | 160 | While you can use the i2c types to directly talk to i2c devices, the specific device may already have higher-level support in the 161 | hwio/devices package, so check there first, as the hard work may be done already. 162 | 163 | ## PWM 164 | 165 | PWM support for BeagleBone Black has been added. To use a PWM pin, you need to fetch the module that the PWM belongs to, 166 | enable the PWM module and pin, and then you can manipulate the period and duty cycle. e.g. 167 | 168 | // Get the module 169 | m, e := hwio.GetModule("pwm2") 170 | if e != nil { 171 | fmt.Printf("could not get pwm2 module: %s\n", e) 172 | return 173 | } 174 | 175 | pwm := m.(hwio.PWMModule) 176 | 177 | // Enable it. 178 | pwm.Enable() 179 | 180 | // Get the PWM pin 181 | pwm8_13, _ := hwio.GetPin("P8.13") 182 | e = pwm.EnablePin(pwm8_13, true) 183 | if e != nil { 184 | fmt.Printf("Error enabling pin: %s\n", e) 185 | return 186 | } 187 | 188 | // Set the period and duty cycle, in nanoseconds. This is a 1/10th second cycle 189 | pwm.SetPeriod(pwm8_13, 100000000) 190 | pwm.SetDuty(pwm8_13, 90000000) 191 | 192 | On BeagleBone Black, there are 3 PWM modules, "pwm0", "pwm1" and "pwm2". I am not sure if "pwm1" pins can be assigned, 193 | as they are pre-allocated in the default device tree config, but in theory it should be possible to use them. By 194 | default, these pins can be used: 195 | 196 | * pwm0: P9.21 (ehrpwm0B) and P9.22 (ehrpwm0A) 197 | * pwm2: P8.13 (ehrpwm2A) and P8.19 (ehrpwm2A) 198 | 199 | This is a preliminary implementation; only P8.13 (pwm2) has been tested. PWM pins are not present in default device tree. 200 | The module will add them dynamically as necessary to bonemgr/slots; this will override defaults. 201 | 202 | ## Servo 203 | 204 | There is a servo implementation in the hwio/servo package. See README.md in that package. 205 | 206 | ## Devices 207 | 208 | There are sub-packages under 'devices' that have been made to work with hwio. The currently supported devices include: 209 | 210 | * GY-520 gyroscope/accelerometer using I2C. 211 | * HD-44780 multi-line LCD display. Currently implemented over I2C converter only. 212 | * MCP23017 16-bit port extender over I2C. 213 | * Nintendo Nunchuck over I2C. 214 | 215 | See README.md files in respective directories. 216 | 217 | ## CPU Info 218 | 219 | The helper function CpuInfo can tell you properties about your device. This is based on /proc/cpuinfo. 220 | 221 | e.g. 222 | model := hwio.CpuInfo(0, "model name") 223 | 224 | The properties available from device to device. Processor 0 is always present. 225 | 226 | ## Driver Selection 227 | 228 | The intention of the hwio library is to use uname to attempt to detect the platform and select an appropriate driver (see drivers section below), 229 | so for some platforms this may auto-detect. However, with the variety of boards around and the variety of operating systems, you may find that autodetection 230 | doesn't work. If you need to set the driver automatically, you can do: 231 | 232 | hwio.SetDriver(new(BeagleBoneBlackDriver)) 233 | 234 | This needs to be done before any other hwio calls. 235 | 236 | 237 | ## BIG SHINY DISCLAIMER 238 | 239 | REALLY IMPORTANT THINGS TO KNOW ABOUT THIS ABOUT THIS LIBRARY: 240 | 241 | * It is under development. If you're lucky, it might work. It should be considered 242 | Alpha. 243 | * If you don't want to risk frying your board, you can still run the 244 | unit tests ;-) 245 | 246 | 247 | ## Board Support 248 | 249 | Currently there are 3 drivers: 250 | 251 | * BeagleBoneBlackDriver - for BeagleBone boards running linux kernel 3.7 or 252 | higher, including BeagleBone Black. This is untested on older BeagleBone 253 | boards with updated kernels. 254 | * RaspberryPiDTDriver - for Raspberry Pi modules running linux kernel 3.7 or 255 | higher, which includes newer Raspian kernels and some late Occidental 256 | kernels. 257 | * TestDriver - for unit tests. 258 | 259 | Old pre-kernel-3.7 drivers for BeagleBone and Raspberry Pi have been deprecated as I have no test beds for these. If you want 260 | to use these, you can check out the 'legacy' branch that contains the older drivers, but no new features will be added. 261 | 262 | ### BeagleBoneBlackDriver 263 | 264 | This driver accesses hardware via the device interfaces exposed in the file 265 | system on linux kernels 3.8 or higher, where device tree is mandated. This should be a robust driver as the hardware access is maintained by device 266 | driver authors, but is likely to be not as fast as direct memory I/O to the hardware as there is file system overhead. 267 | 268 | Status: 269 | 270 | * In active development. 271 | * Tested for gpio reads and writes, analog reads and i2c. Test device was BeagleBone Black running rev A5C, running angstrom. 272 | * Driver automatically blocks out the GPIO pins that are allocated to LCD and MMC on the default BeagleBone Black boards. 273 | * GPIOs not assigned at boot to other modules are known to read and write. 274 | * PWM is known to work on erhpwm2A and B ports. 275 | * GPIO pull-ups is not yet supported. 276 | * i2c is enabled by default. 277 | * Has not been tested on BeagleBone Black rev C 278 | 279 | ### RaspberryPiDTDriver 280 | 281 | This driver is very similar to the BeagleBone Black driver in that it uses the modules compiled into the kernel and 282 | configured using device tree. It uses the same GPIO and i2c implementatons, just with different pins. 283 | 284 | Current status: 285 | 286 | * DigitalRead and DigitalWrite (GPIO) have been tested and work correctly on supported GPIO pins. Test platform was 287 | Raspberry Pi (revision 1), Raspian 2013-12-20-wheezy-raspbian, kernel 3.10.24+. 288 | * GPIO pins are gpio4, gpio17, gpio18, gpio21, gpio22, gpio23, gpio24 and gpio25. 289 | * I2C is working on raspian. You need to enable it on the board first. 290 | Follow [these instructions](http://www.abelectronics.co.uk/i2c-raspbian-wheezy/info.aspx "i2c and spi support on raspian") 291 | * It is unlikely to work on a Raspberry Pi B+, as many pins have moved, 292 | even on the first 26 legacy pins. Power and I2C appear to be in the same 293 | locations, but little else. 294 | 295 | GetPin references on this driver return the pin numbers that are on the headers. Pin 0 is unimplemented. 296 | 297 | Note: before using this, check your kernel is 3.7 or higher. There are a number of pre-3.7 distributions still in use, and this driver 298 | does not support pre-3.7. 299 | 300 | ### OdroidC1Driver 301 | 302 | This driver accesses hardware via the device interfaces exposed in the file system on linux kernels 3.8 or higher, where device tree is mandated. This should be a robust driver as the hardware access is maintained by device driver authors, but is likely to be not as fast as direct memory I/O to the hardware as there is file system overhead. 303 | 304 | Status: 305 | 306 | * In active development. 307 | * Analog input is known to work. Device limit is 1.8V on analog input. 308 | * Autodetection works 309 | * GPIO not fully tested 310 | * PWM does not work yet 311 | 312 | I2C is not loaded by default on this device. You need to either: 313 | 314 | modprobe aml_i2c 315 | 316 | or to enable on each boot: 317 | 318 | sudo echo "aml_i2c" >> /etc/modules 319 | 320 | This makes the necessary /dev/i2c* files appear. 321 | 322 | ## Implementation Notes 323 | 324 | Some general principles the library attempts to adhere to include: 325 | 326 | * Pin references are logical, and are mapped to hardware pins by the driver. The pin 327 | numbers you get back from GetPin are, unless otherwise specified, related to the pin numbers 328 | on extension headers. 329 | * Drivers provide pin names, so you can look them up by meaningful names 330 | instead of relying on device specific numbers. On boards such as BeagleBone, where pins 331 | are multiplexed to internal functions, pins can have multiple names. 332 | * The library does not implement Arduino functions for their own sake if go's 333 | framework naturally supports them better, unless we can provide a simpler interface 334 | to those functions and keep close to the Arduino semantics. 335 | * Drivers are very thin layers; most of the I/O functionality is provided by **modules**. 336 | These aim to be as generic as possible so that different drivers on similar kernels can 337 | assemble the modules that are enabled in device tree, with appropriate pin configuration. 338 | This also makes it easier to add in new modules to support various SoC functions. 339 | * Make no assumption about the state of a pin whose mode has not been set. 340 | Specifically, pins that don't have mode set may not on a particular hardware 341 | configuration even be configured as general purpose I/O. For example, many 342 | beaglebone pins have overloaded functions set using a multiplexer, and some are be pre-assigned 343 | by the default device tree configuration. 344 | * Any pin whose mode is set by PinMode can be assumed to be general purpose I/O, and 345 | likewise if it is not set, it could have any multiplexed behaviour assigned 346 | to it. A consequence is that unlike Arduino, PinMode *must* be called before 347 | a pin is used. 348 | * The library should be as fast as possible so that applications that require 349 | very high speed I/O should achieve maximal throughput, given an appropriate 350 | driver. 351 | * Make simple stuff simple, and harder stuff possible. In particular, while 352 | Arduino-like methods have uniform interface and semantics across drivers, 353 | we don't hide the driver itself or the modules it uses, so special features of a driver or module 354 | can still be used, albeit non-portably. 355 | * Sub-packages can be added as required that approximately parallel Arduino 356 | libaries (e.g. perhaps an SD card package). 357 | 358 | 359 | ### Pins 360 | 361 | Pins are logical representation of physical pins on the hardware. To provide 362 | some abstraction, pins are numbered, much like on an Arduino. Unlike Arduino, 363 | there is no single mapping to hardware pins - this is done by the hardware 364 | driver. To make it easier to work with, drivers can give one or more names to 365 | a pin, and you can use GetPin to get a reference to the pin by one of those 366 | names. 367 | 368 | Each driver must implement a method that defines the mapping from logical pins 369 | to physical pins as understood by that piece of hardware. Additionally, the 370 | driver also publishes the modules that the hardware configuration supports, so 371 | that hwio can ensure that constraints of the hardware are met. For example, if a 372 | pin implements PWM and GPIO in hardware, it is associated with two modules. When 373 | the PWM module is enabled, it will assign the pin to itself. Because each 374 | pin can have a different set of capabilities, there is no distinction between 375 | analog and digital pins as there is in Arduino; there is one set of pins, which 376 | may support any number of capabilities including digital and analog. 377 | 378 | The caller generally works with logical pin numbers retrieved by GetPin. 379 | 380 | 381 | ## Things to be done 382 | 383 | * Interupts (lib, BeagleBone and R-Pi) 384 | * Serial support for UART pins (lib, BeagleBone and R-Pi) 385 | * SPI support; consider augmenting ShiftIn and ShiftOut to use hardware pins 386 | if appropriate (Beaglebone and R-Pi) 387 | * Stepper (lib) 388 | * TLC5940 (lib) 389 | -------------------------------------------------------------------------------- /cpuinfo.go: -------------------------------------------------------------------------------- 1 | // Contains a helper function for getting named properties out of the /dev/cpuinfo file. 2 | // The file is only opened once. Properties are stored per processor. Processor 0 should 3 | // be guaranteed to be present. 4 | 5 | package hwio 6 | 7 | import ( 8 | "bufio" 9 | "fmt" 10 | "os" 11 | "strings" 12 | ) 13 | 14 | // maps processor:property to value. 15 | var cpuInfo map[string]string 16 | 17 | // Look up property for a CPU. CPU's start at 0. 18 | func CpuInfo(cpu int, property string) string { 19 | if cpuInfo == nil { 20 | loadCpuInfo() 21 | } 22 | 23 | return cpuInfo[fmt.Sprintf("%d:%s", cpu, property)] 24 | } 25 | 26 | func loadCpuInfo() { 27 | cpuInfo = make(map[string]string) 28 | 29 | file, e := os.Open("/proc/cpuinfo") 30 | if e != nil { 31 | return 32 | } 33 | 34 | currentCpu := "" 35 | 36 | scanner := bufio.NewScanner(file) 37 | for scanner.Scan() { 38 | line := scanner.Text() 39 | 40 | // split on the first colon, and trim both sides 41 | i := strings.Index(line, ":") 42 | if i >= 0 { 43 | name := strings.Trim(line[0:i], " \t") 44 | value := strings.Trim(line[i+1:], " \t") 45 | 46 | if name == "processor" { 47 | currentCpu = value 48 | } 49 | cpuInfo[currentCpu+":"+name] = value 50 | } 51 | 52 | } 53 | 54 | if err := scanner.Err(); err != nil { 55 | panic(err) 56 | } 57 | } 58 | -------------------------------------------------------------------------------- /devices/bh1750fvi/README.md: -------------------------------------------------------------------------------- 1 | # BH1750FVI Light sensor I2C 2 | 3 | This provides a simple way to access the sensor values of a BH1750FVI light sensor that is connected to an i2c bus on your system. 4 | 5 | # Usage 6 | 7 | Import the packages: 8 | 9 | // import the require modules 10 | import( 11 | "github.com/mrmorphic/hwio" 12 | "github.com/mrmorphic/hwio/devices/bh1750fvi" 13 | ) 14 | 15 | Initialise by fetching an i2c module from the driver. You can get instances of devices attached to 16 | the bus. 17 | 18 | // Get the i2c module from the driver. This is an example for the BeagleBone Black, which exposes i2c2. 19 | m, e := hwio.GetModule("i2c2") 20 | 21 | // Assert that it is an I2C module 22 | i2c := m.(I2CModule) 23 | 24 | Get the device, so you make requests of it: 25 | 26 | // Get a gyro device on this i2c bus, using the default address (0x23) 27 | lightSensor := bh1750fvi.NewBH1750FVI(i2c) 28 | 29 | // Or if you want to use the alternate address (0x5c), use: 30 | lightSensor := bh1750fvi.NewBH1750FVIAddr(i2c, bh1750fvi.DEVICE_ADDRESS_ADDR_HIGH) 31 | 32 | 33 | Read values from the device whenever you want to: 34 | 35 | value, e := lightSensor.ReadLightLevel(bh1750fvi.ONETIME_HIGH_RES) 36 | 37 | The return value is a float32, which is the measure of lux. 38 | 39 | The device supports a number of modes, which are declared as constants: 40 | 41 | * CONTINUOUS_HIGH_RES 42 | * CONTINUOUS_HIGH_RES_2 43 | * CONTINUOUS_LOW_RES 44 | * ONETIME_HIGH_RES 45 | * ONETIME_HIGH_RES_2 46 | * ONETIME_LOW_RES 47 | 48 | The read process takes variable time based on which mode is used. Consult the datasheet for the device 49 | to see what the modes mean and the sampling time. -------------------------------------------------------------------------------- /devices/bh1750fvi/bh1750fvi.go: -------------------------------------------------------------------------------- 1 | // Support for BH1750FVI light sensor. 2 | 3 | package bh1750fvi 4 | 5 | import ( 6 | "github.com/mrmorphic/hwio" 7 | ) 8 | 9 | type ReadMode int 10 | 11 | type modeConfig struct { 12 | deviceMode byte 13 | sampleTimeMs int 14 | } 15 | 16 | const ( 17 | // This is the default address 18 | DEVICE_ADDRESS_ADDR_LOW = 0x23 // A0 = L 19 | DEVICE_ADDRESS_ADDR_HIGH = 0x5c // A0 = H 20 | ) 21 | 22 | const ( 23 | // These are the modes supported by the device. 24 | CONTINUOUS_HIGH_RES ReadMode = iota 25 | CONTINUOUS_HIGH_RES_2 26 | CONTINUOUS_LOW_RES 27 | ONETIME_HIGH_RES 28 | ONETIME_HIGH_RES_2 29 | ONETIME_LOW_RES 30 | ) 31 | 32 | var modes map[ReadMode]modeConfig 33 | 34 | type BH1750FVI struct { 35 | device hwio.I2CDevice 36 | } 37 | 38 | func init() { 39 | // Set up modes 40 | modes = make(map[ReadMode]modeConfig) 41 | modes[CONTINUOUS_HIGH_RES] = modeConfig{0x10, 180} 42 | modes[CONTINUOUS_HIGH_RES_2] = modeConfig{0x11, 180} 43 | modes[CONTINUOUS_LOW_RES] = modeConfig{0x13, 24} 44 | modes[ONETIME_HIGH_RES] = modeConfig{0x20, 180} 45 | modes[ONETIME_HIGH_RES_2] = modeConfig{0x21, 180} 46 | modes[ONETIME_LOW_RES] = modeConfig{0x23, 24} 47 | } 48 | 49 | // Create a new device, with i2c address specified. This can be used to access the device 50 | // on a non-standard address, since it has an address bit. 51 | func NewBH1750FVIAddr(module hwio.I2CModule, address int) *BH1750FVI { 52 | device := module.GetDevice(address) 53 | result := &BH1750FVI{device: device} 54 | 55 | return result 56 | } 57 | 58 | // Create a new device with the default i2c address (A0 is low on the board) 59 | func NewBH1750FVI(module hwio.I2CModule) *BH1750FVI { 60 | return NewBH1750FVIAddr(module, DEVICE_ADDRESS_ADDR_LOW) 61 | } 62 | 63 | // Read the light level in low resolution mode, which is to a 4 lux precision. 64 | func (t *BH1750FVI) ReadLightLevel(mode ReadMode) (float32, error) { 65 | // Get the settings 66 | m := modes[mode] 67 | 68 | // send a command to initiate low resolution read. The empty slice indicates there are no additional bytes, 69 | // just the command 70 | t.device.Write(m.deviceMode, []byte{}) 71 | 72 | // wait for the sampling to be complete, max of 24ms 73 | hwio.Delay(m.sampleTimeMs) 74 | 75 | // read two bytes 76 | // @todo verify if this is correct. From Arduino examples I've seen, they use beginTransmission with the address, 77 | // @todo then requestFrom with the address. However the address 0x23 is also a device register. Need to check this. 78 | buffer, e := t.device.Read(m.deviceMode, 2) 79 | if e != nil { 80 | return 0, e 81 | } 82 | MSB := buffer[0] 83 | LSB := buffer[1] 84 | 85 | /* Convert 12bit int using two's compliment */ 86 | /* Credit: http://bildr.org/2011/01/tmp102-arduino/ */ 87 | level := ((int(MSB) << 8) | int(LSB)) 88 | 89 | // divide by 16, since lowest 4 bits are fractional. 90 | return float32(level), nil 91 | } 92 | -------------------------------------------------------------------------------- /devices/gy520/README.md: -------------------------------------------------------------------------------- 1 | # GY-520 (MPU-6050) I2C 2 | 3 | This provides a simple way to access the sensor values of a GY-520 that is connected to an i2c bus on your system. 4 | 5 | # Usage 6 | 7 | Import the packages: 8 | 9 | // import the require modules 10 | import( 11 | "github.com/mrmorphic/hwio" 12 | "github.com/mrmorphic/hwio/devices/gy520" 13 | ) 14 | 15 | Initialise by fetching an i2c module from the driver. You can get instances of devices attached to 16 | the bus. 17 | 18 | // Get the i2c module from the driver. This is an example for the BeagleBone Black, which exposes i2c2. 19 | m, e := hwio.GetModule("i2c2") 20 | 21 | // Assert that it is an I2C module 22 | i2c := m.(I2CModule) 23 | 24 | Get the GY520 device, so you make requests of it: 25 | 26 | // Get a gyro device on this i2c bus 27 | gyro := gy520.NewGY520(i2c) 28 | 29 | // gyro is asleep by default, to save power 30 | gyro.Wake() 31 | 32 | Read values from the device whenever you want to: 33 | 34 | // Get the gyroscope x, y and z sensor values 35 | gx, gy, gz, e := gyro.GetGyro() 36 | 37 | // Get the accelerometer x, y and z sensor values 38 | ax, ay, az, e := gyro.GetAccel() 39 | 40 | // Get the temperature 41 | temp, e := gyro.GetTemp() 42 | 43 | Note that you will need to calibrate your device to make sense of the values coming out. -------------------------------------------------------------------------------- /devices/gy520/gy520.go: -------------------------------------------------------------------------------- 1 | // Support for GY-520 gyroscope. 2 | 3 | // Current status: 4 | // - only supports small subset of what the device (MPU-6050) is capable of. In particular, only supports gyroscope, 5 | // accelerometer and temperature spot data, and has no support for FIFO, interupt or slaves. 6 | // - it is being included as an example of how I2C devices can be added to the hwio package, and hopefully over time this 7 | // driver will be exended and more devices will be supported. 8 | 9 | package gy520 10 | 11 | import ( 12 | "github.com/mrmorphic/hwio" 13 | ) 14 | 15 | const ( 16 | // This is the default address. Some devices may also respond to 0x69 17 | DEVICE_ADDRESS = 0x68 18 | 19 | REG_CONFIG = 0x1a 20 | REG_GYRO_CONFIG = 0x1b 21 | REG_ACCEL_CONFIG = 0x1c 22 | 23 | // accelerometer sensor registers, read-only 24 | REG_ACCEL_XOUT_H = 0x3b 25 | REG_ACCEL_XOUT_L = 0x3c 26 | REG_ACCEL_YOUT_H = 0x3d 27 | REG_ACCEL_YOUT_L = 0x3e 28 | REG_ACCEL_ZOUT_H = 0x3f 29 | REG_ACCEL_ZOUT_L = 0x40 30 | 31 | // temperature sensor registers, read-only 32 | REG_TEMP_OUT_H = 0x41 33 | REG_TEMP_OUT_L = 0x42 34 | 35 | // gyroscope sensor registers, read-only 36 | REG_GYRO_XOUT_H = 0x43 37 | REG_GYRO_XOUT_L = 0x44 38 | REG_GYRO_YOUT_H = 0x45 39 | REG_GYRO_YOUT_L = 0x46 40 | REG_GYRO_ZOUT_H = 0x47 41 | REG_GYRO_ZOUT_L = 0x48 42 | 43 | REG_PWR_MGMT_1 = 0x6b 44 | 45 | PARAM_SLEEP = 0x40 46 | ) 47 | 48 | type GY520 struct { 49 | device hwio.I2CDevice 50 | } 51 | 52 | func NewGY520(module hwio.I2CModule) *GY520 { 53 | device := module.GetDevice(DEVICE_ADDRESS) 54 | result := &GY520{device: device} 55 | 56 | return result 57 | } 58 | 59 | // Wake the device. By default on power on, the device is asleep. 60 | func (g *GY520) Wake() error { 61 | v, e := g.device.ReadByte(REG_PWR_MGMT_1) 62 | if e != nil { 63 | return e 64 | } 65 | 66 | v &= ^byte(PARAM_SLEEP) 67 | 68 | e = g.device.WriteByte(REG_PWR_MGMT_1, v) 69 | if e != nil { 70 | return e 71 | } 72 | 73 | return nil 74 | } 75 | 76 | // Put the device back to sleep. 77 | func (g *GY520) Sleep() error { 78 | v, e := g.device.ReadByte(REG_PWR_MGMT_1) 79 | if e != nil { 80 | return e 81 | } 82 | 83 | v |= ^byte(PARAM_SLEEP) 84 | 85 | e = g.device.WriteByte(REG_PWR_MGMT_1, v) 86 | if e != nil { 87 | return e 88 | } 89 | 90 | return nil 91 | } 92 | 93 | func (g *GY520) GetGyro() (gyroX int, gyroY int, gyroZ int, e error) { 94 | buffer, e := g.device.Read(REG_GYRO_XOUT_H, 6) 95 | if e != nil { 96 | return 0, 0, 0, e 97 | } 98 | 99 | gyroX = int(int16(hwio.UInt16FromUInt8(buffer[0], buffer[1]))) 100 | gyroY = int(int16(hwio.UInt16FromUInt8(buffer[2], buffer[3]))) 101 | gyroZ = int(int16(hwio.UInt16FromUInt8(buffer[4], buffer[5]))) 102 | 103 | return gyroX, gyroY, gyroZ, nil 104 | } 105 | 106 | func (g *GY520) GetAccel() (accelX int, accelY int, accelZ int, e error) { 107 | buffer, e := g.device.Read(REG_ACCEL_XOUT_H, 6) 108 | if e != nil { 109 | return 0, 0, 0, e 110 | } 111 | 112 | accelX = int(int16(hwio.UInt16FromUInt8(buffer[0], buffer[1]))) 113 | accelY = int(int16(hwio.UInt16FromUInt8(buffer[2], buffer[3]))) 114 | accelZ = int(int16(hwio.UInt16FromUInt8(buffer[4], buffer[5]))) 115 | 116 | return accelX, accelY, accelZ, nil 117 | } 118 | 119 | func (g *GY520) GetTemp() (int, error) { 120 | buffer, e := g.device.Read(REG_TEMP_OUT_H, 2) 121 | if e != nil { 122 | return 0, e 123 | } 124 | 125 | return int(int16(hwio.UInt16FromUInt8(buffer[0], buffer[1]))), nil 126 | } 127 | 128 | func (g *GY520) SetAccelSampleRate(rate int) { 129 | 130 | } 131 | 132 | func (g *GY520) SetGyroSampleRate(rate int) { 133 | 134 | } 135 | 136 | func (g *GY520) SetTempSampleRate(rate int) { 137 | 138 | } 139 | -------------------------------------------------------------------------------- /devices/hd44780/README.md: -------------------------------------------------------------------------------- 1 | # HD44780 over I2C 2 | 3 | This provides control of an HD44780-compatible device that is connected via an I2C expander. 4 | 5 | # Usage 6 | 7 | Import the packages: 8 | 9 | // import the require modules 10 | import( 11 | "github.com/mrmorphic/hwio" 12 | "github.com/mrmorphic/hwio/devices/hd44780" 13 | ) 14 | 15 | Initialise by fetching an i2c module from the driver. You can get instances of devices attached to 16 | the bus. 17 | 18 | // Get the i2c module from the driver. i2c is the canonical name. On the BeagleBone, it can also 19 | // be referred to as i2c2. 20 | m, e := hwio.GetModule("i2c") 21 | 22 | // Assert that it is an I2C module 23 | i2c := m.(hwio.I2CModule) 24 | 25 | 26 | Get the HD44780 device, so you make requests of it: 27 | 28 | // Get a display device on this i2c bus. You need to provide it a device profile. Two profiles 29 | // are currently implemented, PROFILE_MJKDZ and PROFILE_PCF8574, corresponding to two commonly found 30 | // port extenders used to interface to LCD displays. 31 | display := hd44780.NewHD44780(i2c, 0x20, hd44780.PROFILE_MJKDZ) 32 | 33 | // Initialise the display with the size of display you have. 34 | display.Init(20, 4) 35 | 36 | // The display may not show anything if the backlight is turned off 37 | display.SetBacklight(true) 38 | 39 | // If you want to see the cursor, you can turn it on 40 | display.Cursor() 41 | 42 | To display things, you can: 43 | 44 | // clear the display 45 | display.Clear() 46 | 47 | // Set cursor back to (0,0) 48 | display.Home() 49 | 50 | // Set cursor to a specific column and row (both zero based) 51 | display.SetCursor(0, 1) // second line 52 | 53 | // output a single character 54 | display.Data('A') 55 | 56 | // Use any function that expects a Writer to output to the display. This is because the HD44780 type 57 | // implements Writer interface. 58 | fmt.Fprintf(display, "Hi, %s", name) 59 | 60 | Note that characters that are output to the device are not necessarily displayed consequetively. In particular wrapping may not work 61 | as you expect. This is because of how the display unit maps it's display buffer to positions on the screen. This is described in 62 | the datasheet for the HD44780 unit. 63 | 64 | An alternative way to create the device is to use NewHD44780Extended instead of NewHD44780. This is useful if you have an i2c extender that 65 | does not confirm to the builtin profiles: 66 | 67 | display := NewHD44780Extended(i2c, 0x27, 68 | 0, // en 69 | 1, // rw 70 | 2, // rs 71 | 4, // d4 72 | 5, // d5 73 | 6, // d6 74 | 7, // d7polarity int) *HD44780 { 75 | 3, // backlight, 76 | hd44780.POSITIVE) // polarity 77 | 78 | The pin values are the bit positions for that pin, with 7 being MSB and0 being LSB. The underlying assumption 79 | is that the port extender is 8 bit. This package will not work for 16 bit extenders, for example. 80 | 81 | # Notes 82 | 83 | This has been tested on an mjkdz i2c expander and a 20x4 character display, and works correctly. Other LCD i2c expanders 84 | may map En, Rw and Rs pins differently, however. If you have issues with a different I2C device, let me know, or submit 85 | a pull request with the configuration that works for you. 86 | -------------------------------------------------------------------------------- /devices/hd44780/hd44780_i2c.go: -------------------------------------------------------------------------------- 1 | // An implementation of the HD44780 display communicating through an I2C expander. This is based on the I2C display library for 2 | // Arduino: http://hmario.home.xs4all.nl/arduino/LiquidCrystal_I2C/ 3 | // 4 | // The hardware takes the form of an HD44780 that is connected to I2C by a port expander such as an PCF8574, an 8-bit port expander. 5 | // Typically it is connected to the display unit using 4 bits for data, and the other bits for RS, RW, EN and backlight control. 6 | 7 | // This has been tested against a mjkdz brand adaptor, and works correctly. In other bits of Arduino code the En, Rw and Rs bits were assigned to 8 | // the port expander differently. The display I tested was a 20x4 unit. Note characters output to the display are not necessarily displayed 9 | // adjacently; you need to understand how the DRAM on the display maps to characters on the display. 10 | 11 | package hd44780 12 | 13 | import ( 14 | "github.com/mrmorphic/hwio" 15 | ) 16 | 17 | const ( 18 | // commands 19 | LCD_CLEARDISPLAY byte = 0x01 20 | LCD_RETURNHOME byte = 0x02 21 | LCD_ENTRYMODESET byte = 0x04 22 | LCD_DISPLAYCONTROL byte = 0x08 23 | LCD_CURSORSHIFT byte = 0x10 24 | LCD_FUNCTIONSET byte = 0x20 25 | LCD_SETCGRAMADDR byte = 0x40 26 | LCD_SETDDRAMADDR byte = 0x80 27 | 28 | // flags for display entry mode 29 | LCD_ENTRYRIGHT byte = 0x00 30 | LCD_ENTRYLEFT byte = 0x02 31 | LCD_ENTRYSHIFTINCREMENT byte = 0x01 32 | LCD_ENTRYSHIFTDECREMENT byte = 0x00 33 | 34 | // flags for display on/off control 35 | LCD_DISPLAYON byte = 0x04 36 | LCD_DISPLAYOFF byte = 0x00 37 | LCD_CURSORON byte = 0x02 38 | LCD_CURSOROFF byte = 0x00 39 | LCD_BLINKON byte = 0x01 40 | LCD_BLINKOFF byte = 0x00 41 | 42 | // flags for display/cursor shift 43 | LCD_DISPLAYMOVE byte = 0x08 44 | LCD_CURSORMOVE byte = 0x00 45 | LCD_MOVERIGHT byte = 0x04 46 | LCD_MOVELEFT byte = 0x00 47 | 48 | // flags for function set 49 | LCD_8BITMODE byte = 0x10 50 | LCD_4BITMODE byte = 0x00 51 | LCD_2LINE byte = 0x08 52 | LCD_1LINE byte = 0x00 53 | LCD_5x10DOTS byte = 0x04 54 | LCD_5x8DOTS byte = 0x00 55 | 56 | // flags for backlight control 57 | // LCD_BACKLIGHT byte = 0x00 58 | // LCD_NOBACKLIGHT byte = 0x80 59 | 60 | // En byte = 0x10 // B00010000 // Enable bit 61 | // Rw byte = 0x20 // B00100000 // Read/Write bit 62 | // Rs byte = 0x40 // B01000000 // Register select bit 63 | // // En byte = 0x40 // B00010000 // Enable bit 64 | // // Rw byte = 0x20 // B00100000 // Read/Write bit 65 | // // Rs byte = 0x10 // B01000000 // Register select bit 66 | 67 | // constants for backlight polarity 68 | POSITIVE = 0 69 | NEGATIVE = 1 70 | ) 71 | 72 | type HD44780 struct { 73 | device hwio.I2CDevice 74 | displayFunction byte 75 | displayControl byte 76 | displayMode byte 77 | numLines int 78 | backlight byte 79 | 80 | // the bit masks of the LCD pins on the port extender. 81 | d7 byte 82 | d6 byte 83 | d5 byte 84 | d4 byte 85 | bl byte 86 | en byte 87 | rs byte 88 | rw byte 89 | 90 | blPolarity int 91 | } 92 | 93 | type I2CExpanderProfile int 94 | 95 | const ( 96 | // Profile constants for pre-defined device profiles. See http://forum.arduino.cc/index.php?topic=158312.15 97 | 98 | // mjkdz devices are commonly found in the wild 99 | PROFILE_MJKDZ I2CExpanderProfile = iota 100 | 101 | // devices based on PCF8574 are also around, but wired a little bit differently. 102 | PROFILE_PCF8574 103 | ) 104 | 105 | func NewHD44780(module hwio.I2CModule, address int, profile I2CExpanderProfile) *HD44780 { 106 | switch profile { 107 | case PROFILE_MJKDZ: 108 | return NewHD44780Extended(module, address, 4, 5, 6, 0, 1, 2, 3, 7, NEGATIVE) 109 | case PROFILE_PCF8574: 110 | return NewHD44780Extended(module, address, 2, 1, 0, 4, 5, 6, 7, 3, POSITIVE) 111 | } 112 | 113 | return nil 114 | } 115 | 116 | func NewHD44780Extended(module hwio.I2CModule, address int, en int, rw int, rs int, d4 int, d5 int, d6 int, d7 int, bl int, polarity int) *HD44780 { 117 | device := module.GetDevice(address) 118 | result := &HD44780{ 119 | device: device, 120 | d7: 1 << uint16(d7), 121 | d6: 1 << uint16(d6), 122 | d5: 1 << uint16(d5), 123 | d4: 1 << uint16(d4), 124 | bl: 1 << uint16(bl), 125 | en: 1 << uint16(en), 126 | rs: 1 << uint16(rs), 127 | rw: 1 << uint16(rw), 128 | blPolarity: polarity} 129 | 130 | return result 131 | } 132 | 133 | func (display *HD44780) Init(cols int, lines int) { 134 | display.displayFunction = LCD_4BITMODE | LCD_1LINE | LCD_5x8DOTS 135 | 136 | if lines > 1 { 137 | display.displayFunction |= LCD_2LINE 138 | } 139 | display.numLines = lines 140 | 141 | // for some 1 line displays you can select a 10 pixel high font 142 | // if (dotsize != 0) && (lines == 1) { 143 | // _displayfunction |= LCD_5x10DOTS 144 | // } 145 | 146 | // SEE PAGE 45/46 FOR INITIALIZATION SPECIFICATION! 147 | // according to datasheet, we need at least 40ms after power rises above 2.7V 148 | // before sending commands. Arduino can turn on way befer 4.5V so we'll wait 50 149 | hwio.DelayMicroseconds(50000) 150 | 151 | // Now we pull both RS and R/W low to begin commands 152 | display.backlight = display.bl 153 | display.expanderWrite(display.backlight) // reset expander and turn backlight off (Bit 8 =1) 154 | hwio.Delay(1000) 155 | 156 | //put the LCD into 4 bit mode 157 | // this is according to the hitachi HD44780 datasheet 158 | // figure 24, pg 46 159 | 160 | // we start in 8bit mode, try to set 4 bit mode 161 | display.write4bits(0x03, 0) 162 | hwio.DelayMicroseconds(4500) // wait min 4.1ms 163 | 164 | // // second try 165 | display.write4bits(0x03, 0) 166 | hwio.DelayMicroseconds(4500) // wait min 4.1ms 167 | 168 | // // third go! 169 | display.write4bits(0x03, 0) 170 | hwio.DelayMicroseconds(150) 171 | 172 | // // finally, set to 4-bit interface 173 | display.write4bits(0x02, 0) 174 | 175 | // set # lines, font size, etc. 176 | display.Command(LCD_FUNCTIONSET | display.displayFunction) 177 | 178 | // turn the display on with no cursor or blinking default 179 | display.displayControl = LCD_DISPLAYON | LCD_CURSOROFF | LCD_BLINKOFF 180 | display.Display() 181 | 182 | // clear it off 183 | display.Clear() 184 | 185 | // Initialize to default text direction (for roman languages) 186 | display.displayMode = LCD_ENTRYLEFT | LCD_ENTRYSHIFTDECREMENT 187 | 188 | // set the entry mode 189 | display.Command(LCD_ENTRYMODESET | display.displayMode) 190 | 191 | display.Home() 192 | } 193 | 194 | func (display *HD44780) Clear() { 195 | display.Command(LCD_CLEARDISPLAY) // clear display, set cursor position to zero 196 | hwio.DelayMicroseconds(2000) // this command takes a long time! 197 | } 198 | 199 | func (display *HD44780) Home() { 200 | display.Command(LCD_RETURNHOME) // set cursor position to zero 201 | hwio.DelayMicroseconds(2000) // this command takes a long time! 202 | } 203 | 204 | func (display *HD44780) SetCursor(col int, row int) { 205 | rowOffsets := []byte{0x00, 0x40, 0x14, 0x54} 206 | if row > display.numLines { 207 | row = display.numLines - 1 // we count rows starting w/0 208 | } 209 | display.Command(LCD_SETDDRAMADDR | (byte(col) + rowOffsets[row])) 210 | } 211 | 212 | // Turn the display on/off (quickly) 213 | func (display *HD44780) NoDisplay() { 214 | display.displayControl &= ^LCD_DISPLAYON 215 | display.Command(LCD_DISPLAYCONTROL | display.displayControl) 216 | } 217 | 218 | func (display *HD44780) Display() { 219 | display.displayControl |= LCD_DISPLAYON 220 | display.Command(LCD_DISPLAYCONTROL | display.displayControl) 221 | } 222 | 223 | // Turns the underline cursor on/off 224 | func (display *HD44780) NoCursor() { 225 | display.displayControl &= ^LCD_CURSORON 226 | display.Command(LCD_DISPLAYCONTROL | display.displayControl) 227 | } 228 | func (display *HD44780) Cursor() { 229 | display.displayControl |= LCD_CURSORON 230 | display.Command(LCD_DISPLAYCONTROL | display.displayControl) 231 | } 232 | 233 | // Turn on and off the blinking cursor 234 | func (display *HD44780) NoBlink() { 235 | display.displayControl &= ^LCD_BLINKON 236 | display.Command(LCD_DISPLAYCONTROL | display.displayControl) 237 | } 238 | func (display *HD44780) Blink() { 239 | display.displayControl |= LCD_BLINKON 240 | display.Command(LCD_DISPLAYCONTROL | display.displayControl) 241 | } 242 | 243 | // These commands scroll the display without changing the RAM 244 | func (display *HD44780) ScrollDisplayLeft() { 245 | display.Command(LCD_CURSORSHIFT | LCD_DISPLAYMOVE | LCD_MOVELEFT) 246 | } 247 | 248 | func (display *HD44780) ScrollDisplayRight() { 249 | display.Command(LCD_CURSORSHIFT | LCD_DISPLAYMOVE | LCD_MOVERIGHT) 250 | } 251 | 252 | // This is for text that flows Left to Right 253 | func (display *HD44780) LeftToRight() { 254 | display.displayMode |= LCD_ENTRYLEFT 255 | display.Command(LCD_ENTRYMODESET | display.displayMode) 256 | } 257 | 258 | // This is for text that flows Right to Left 259 | func (display *HD44780) RightToLeft() { 260 | display.displayMode &= ^LCD_ENTRYLEFT 261 | display.Command(LCD_ENTRYMODESET | display.displayMode) 262 | } 263 | 264 | // This will 'right justify' text from the cursor 265 | func (display *HD44780) Autoscroll() { 266 | display.displayMode |= LCD_ENTRYSHIFTINCREMENT 267 | display.Command(LCD_ENTRYMODESET | display.displayMode) 268 | } 269 | 270 | // This will 'left justify' text from the cursor 271 | func (display *HD44780) NoAutoscroll() { 272 | display.displayMode &= ^LCD_ENTRYSHIFTINCREMENT 273 | display.Command(LCD_ENTRYMODESET | display.displayMode) 274 | } 275 | 276 | // // Allows us to fill the first 8 CGRAM locations 277 | // // with custom characters 278 | // func (display *HD44780) createChar(location byte, charmap byte[]) { 279 | // location &= 0x7; // we only have 8 locations 0-7 280 | // display.Command(LCD_SETCGRAMADDR | (location << 3)); 281 | // for i := 0; i < 8; i++ { 282 | // write(charmap[i]); 283 | // } 284 | // } 285 | 286 | func (display *HD44780) SetBacklight(on bool) { 287 | if on { 288 | display.backlight = display.bl 289 | } else { 290 | display.backlight = 0 291 | } 292 | display.expanderWrite(0) 293 | } 294 | 295 | func (display *HD44780) Command(command byte) { 296 | display.send(command, 0) 297 | } 298 | 299 | func (display *HD44780) Data(data byte) { 300 | display.send(data, display.rs) 301 | } 302 | 303 | func (display *HD44780) send(data byte, mode byte) { 304 | highnib := data >> 4 305 | lownib := data & 0x0F 306 | display.write4bits(highnib, mode) 307 | display.write4bits(lownib, mode) 308 | } 309 | 310 | // write 4 bits to the port extender. The low 4 bits of data are mapped to the d7-d4 pins on the device, 311 | // so you cannot OR other control bits to the data. Mode is provided for that. 312 | func (display *HD44780) write4bits(data byte, mode byte) { 313 | // map the 4 low bits of data into d 314 | var d byte = 0 315 | if data&0x08 != 0 { 316 | d |= display.d7 317 | } 318 | if data&0x04 != 0 { 319 | d |= display.d6 320 | } 321 | if data&0x02 != 0 { 322 | d |= display.d5 323 | } 324 | if data&0x01 != 0 { 325 | d |= display.d4 326 | } 327 | display.expanderWrite(d | mode) 328 | display.pulseEnable(d | mode) 329 | } 330 | 331 | // Write a byte to the port expander. The bits are already assumed to be in the right positions for 332 | // the device profile. 333 | func (display *HD44780) expanderWrite(data byte) { 334 | display.device.WriteByte(data|display.backlight, 0) 335 | } 336 | 337 | func (display *HD44780) pulseEnable(data byte) { 338 | display.expanderWrite(data | display.en) // En high 339 | hwio.DelayMicroseconds(1) // enable pulse must be >450ns 340 | 341 | display.expanderWrite(data & ^display.en) // En low 342 | hwio.DelayMicroseconds(50) // commands need > 37us to settle 343 | } 344 | 345 | func (display *HD44780) Write(p []byte) (n int, err error) { 346 | for _, b := range p { 347 | display.Data(b) 348 | } 349 | return len(p), nil 350 | } 351 | -------------------------------------------------------------------------------- /devices/mcp23017/README.md: -------------------------------------------------------------------------------- 1 | # MCP-23017 I2C Port Expander 2 | 3 | This package provides a simple way to connect to the MCP-23017 port expander, a device which exposes 2 8-bit 4 | GPIO ports address via I2C. 5 | 6 | # Usage 7 | 8 | Import the packages: 9 | 10 | // import the require modules 11 | import( 12 | "github.com/mrmorphic/hwio" 13 | "github.com/mrmorphic/hwio/devices/mcp23017" 14 | ) 15 | 16 | Initialise by fetching an i2c module from the driver. You can get instances of devices attached to 17 | the bus. 18 | 19 | // Get the i2c module from the driver. This is an example for the BeagleBone Black, which exposes i2c2. 20 | m, e := hwio.GetModule("i2c2") 21 | 22 | // Assert that it is an I2C module 23 | i2c := m.(hwio.I2CModule) 24 | 25 | Get the MCP-23017 device, so you make requests of it: 26 | 27 | // Get the device on this i2c bus. 0 assumes A2, A1 and A0 on the device are grounded. 28 | expander := mcp23017.NewMCP23017(i2c, 0) 29 | 30 | Typically you'll want to get the directions of the pins first: 31 | 32 | expander.SetDirA(0xc0) // set two high bits of port A to inputs, the rest are outputs. 33 | expander.SetDirB(0x00) // set all bits low on port B, to make them all outputs. 34 | 35 | The device has configurable pull-resistors of about 100K that can be enabled (disabled by default): 36 | 37 | expander.SetPullupA(0x40) // set bit 6 of port A to pull-up. It must have been set to an input. 38 | 39 | To read raw input values from the ports: 40 | 41 | v, e := expander.GetPortA() 42 | v2, e = expander.GetPortB() 43 | 44 | To write to output ports: 45 | 46 | e := expander.SetPortA(0x80) 47 | e := expander.SetPortA(0x7b) 48 | -------------------------------------------------------------------------------- /devices/mcp23017/mcp23017.go: -------------------------------------------------------------------------------- 1 | // Support for MCP-23017 I2C port expander. 2 | 3 | // Currently only supports basic GPIO (input and output). It does not support interupt features. 4 | 5 | package mcp23017 6 | 7 | import ( 8 | "fmt" 9 | "github.com/mrmorphic/hwio" 10 | ) 11 | 12 | const ( 13 | // This is the default address if pins A2, A1 and A0 are grounded. So device address is base + (A2, A1, A0) 14 | DEFAULT_BASE_ADDRESS = 0x20 15 | 16 | REG_IODIRA = 0x00 17 | REG_IODIRB = 0x01 18 | REG_IPOLA = 0x02 19 | REG_IPOLB = 0x03 20 | REG_GPINENA = 0x04 21 | REG_GPINENB = 0x05 22 | REG_DEFVALA = 0x06 23 | REG_DEFVALB = 0x07 24 | REG_INTCONA = 0x08 25 | REG_INTCONB = 0x09 26 | REG_IOCON = 0x0a 27 | REG_GPPUA = 0x0c 28 | REG_GPPUB = 0x0d 29 | REG_INTFA = 0x0e 30 | REG_INTFB = 0x0f 31 | REG_INTCAPA = 0x10 32 | REG_INTCAPB = 0x11 33 | REG_GPIOA = 0x12 34 | REG_GPIOB = 0x13 35 | REG_OLATA = 0x14 36 | REG_OLATB = 0x15 37 | ) 38 | 39 | type MCP23017 struct { 40 | device hwio.I2CDevice 41 | } 42 | 43 | // Create a new isntance, and set it to use Bank 0. The address can either be what is wired on 44 | // (A2,A1,A0) of the physical device, in which case this is added to the base address for the device 45 | // (0x20). Otherwise, you can use 0x20-0x27. Anything else will return an error. 46 | func NewMCP23017(module hwio.I2CModule, address int) (*MCP23017, error) { 47 | if address < 8 { 48 | address += DEFAULT_BASE_ADDRESS 49 | } 50 | 51 | if address < 0x20 || address > 0x27 { 52 | return nil, fmt.Errorf("Device address %d is invalid for an MCP23017. It must be in the range 0x20-0x27", address) 53 | } 54 | 55 | device := module.GetDevice(address) 56 | result := &MCP23017{device: device} 57 | 58 | // set config reg, force BANK=0, SEQOP=0. Note that this only works if already in BANK0, which is default on power-up 59 | device.WriteByte(REG_IOCON, 0) 60 | 61 | return result, nil 62 | } 63 | 64 | // Set direction bits for port A. A 1 bit indicates corresponding pin will be an input, 65 | // A 0 bit indicates it will be an output. 66 | func (d *MCP23017) SetDirA(value byte) error { 67 | return d.device.WriteByte(REG_IODIRA, value) 68 | } 69 | 70 | // Set direction bits for port A. A 1 bit indicates corresponding pin will be an input, 71 | // A 0 bit indicates it will be an output. 72 | func (d *MCP23017) SetDirB(value byte) error { 73 | return d.device.WriteByte(REG_IODIRB, value) 74 | } 75 | 76 | // Read from port A 77 | func (d *MCP23017) GetPortA() (byte, error) { 78 | return d.device.ReadByte(REG_GPIOA) 79 | } 80 | 81 | // Read from port B 82 | func (d *MCP23017) GetPortB() (byte, error) { 83 | return d.device.ReadByte(REG_GPIOB) 84 | } 85 | 86 | // Write to port A 87 | func (d *MCP23017) SetPortA(value byte) error { 88 | return d.device.WriteByte(REG_GPIOA, value) 89 | } 90 | 91 | // Write to port B 92 | func (d *MCP23017) SetPortB(value byte) error { 93 | return d.device.WriteByte(REG_GPIOB, value) 94 | } 95 | 96 | // Set pull-up configuration for port A. If a bit is 1 and the corresponding pin is 97 | // an input, a pull-up resistor of about 100K is enabled. A zero bit indicates no pull-up. 98 | func (d *MCP23017) SetPullupA(value byte) error { 99 | return d.device.WriteByte(REG_GPPUA, value) 100 | } 101 | 102 | // Set pull-up configuration for port B. If a bit is 1 and the corresponding pin is 103 | // an input, a pull-up resistor of about 100K is enabled. A zero bit indicates no pull-up. 104 | func (d *MCP23017) SetPullupB(value byte) error { 105 | return d.device.WriteByte(REG_GPPUB, value) 106 | } 107 | -------------------------------------------------------------------------------- /devices/nunchuck/README.md: -------------------------------------------------------------------------------- 1 | # Nintendo Wii Nunchuck over I2C 2 | 3 | This provides a simple way to access the sensor values of a nunchuck that is connected to an i2c bus on your system. 4 | 5 | # Usage 6 | 7 | Import the packages: 8 | 9 | // import the require modules 10 | import( 11 | "github.com/mrmorphic/hwio" 12 | "github.com/mrmorphic/hwio/devices/nunchuck" 13 | ) 14 | 15 | Initialise by fetching an i2c module from the driver. You can get instances of devices attached to 16 | the bus. 17 | 18 | // Get the i2c module from the driver. 19 | m, e := hwio.GetModule("i2c") 20 | 21 | // Assert that it is an I2C module 22 | i2c := m.(hwio.I2CModule) 23 | 24 | Get the nunchuck device, so you make requests of it: 25 | 26 | // Get a gyro device on this i2c bus 27 | controller, e := nunchuck.NewNunchuck(i2c) 28 | if e != nil { 29 | return e 30 | } 31 | 32 | To get values from the nunchuck, you need to call ReadSensors, which fetches all data from the device, and then use Get 33 | methods to fetch the properies you want. 34 | 35 | e := controller.ReadSensors() 36 | 37 | // get joystick values (x2 int) from the last call to ReadSensors() 38 | joyX, joyY := controller.GetJoystick() 39 | 40 | // get accelerometer values (x3 float) from the last call to ReadSensors() 41 | ax, ay, az := controller.GetAccel() 42 | 43 | // get Z button pressed state (bool) from the last call to ReadSensors() 44 | zPressed := controller.GetZPressed() 45 | 46 | // get C button pressed state (bool) from the last call to ReadSensors() 47 | cPressed := controller.GetCPressed() 48 | 49 | // get roll and pitch values (float32) in degrees from the last call to ReadSensors(). Note these are computed from 50 | // the accelerometer values. 51 | roll := controller.GetRoll() 52 | pitch := controller.GetPitch() 53 | 54 | The device joystick and accelerometer values are initially calibrated to zero, but you can change these, and will probably need to. 55 | Until the accelerometer values are calibrated, roll and pitch values may not be meaningful. 56 | 57 | // calibrate the center position of the joystick to whatever the last read joystick values were. 58 | controller.CalibrateJoystick() 59 | 60 | // Set the zero values for the accelerometer to 3 values. 61 | controller.SetAccelZero(zeroX, zeroY, zeroZ) -------------------------------------------------------------------------------- /devices/nunchuck/nunchuck.go: -------------------------------------------------------------------------------- 1 | // Interface for Nintendo Wii nunchucks over I2C. 2 | // With reference to WiiChuck for Arduino. 3 | 4 | package nunchuck 5 | 6 | import ( 7 | "fmt" 8 | "github.com/mrmorphic/hwio" 9 | "math" 10 | ) 11 | 12 | const ( 13 | DEVICE_ADDRESS = 0x52 14 | 15 | DEFAULT_JOYSTICK_ZERO_X = 124 16 | DEFAULT_JOYSTICK_ZERO_Y = 132 17 | 18 | DEFAULT_ACCEL_ZEROX = 0.0 19 | DEFAULT_ACCEL_ZEROY = 0.0 20 | DEFAULT_ACCEL_ZEROZ = 0.0 21 | 22 | RADIUS = 210 23 | ) 24 | 25 | type Nunchuck struct { 26 | device hwio.I2CDevice 27 | zeroJoyX int 28 | zeroJoyY int 29 | zeroAccelX float32 30 | zeroAccelY float32 31 | zeroAccelZ float32 32 | 33 | lastJoyX int 34 | lastJoyY int 35 | lastAccelX float32 36 | lastAccelY float32 37 | lastAccelZ float32 38 | lastZPressed bool 39 | lastCPressed bool 40 | } 41 | 42 | func NewNunchuck(module hwio.I2CModule) (*Nunchuck, error) { 43 | device := module.GetDevice(DEVICE_ADDRESS) 44 | n := &Nunchuck{device: device} 45 | 46 | n.SetJoystickZero(DEFAULT_JOYSTICK_ZERO_X, DEFAULT_JOYSTICK_ZERO_Y) 47 | n.SetAccelZero(DEFAULT_ACCEL_ZEROX, DEFAULT_ACCEL_ZEROY, DEFAULT_ACCEL_ZEROZ) 48 | 49 | // instead of the common 0x40 -> 0x00 initialization, we 50 | // use 0xF0 -> 0x55 followed by 0xFB -> 0x00. 51 | // this lets us use 3rd party nunchucks (like cheap $4 ebay ones) 52 | // while still letting us use official oness. 53 | // see http://www.arduino.cc/cgi-bin/yabb2/YaBB.pl?num=1264805255 54 | 55 | e := device.WriteByte(0xF0, 0x55) // first config register 56 | if e != nil { 57 | return nil, e 58 | } 59 | 60 | hwio.Delay(1) 61 | 62 | e = device.WriteByte(0xFB, 0x00) // second config register 63 | if e != nil { 64 | return nil, e 65 | } 66 | 67 | return n, nil 68 | } 69 | 70 | // Read all sensor values from the nunchuck and reads them into the internal state of the nunchuck instance. 71 | // Use Get methods to retrieve sensor values since last call of ReadSensors. 72 | func (n *Nunchuck) ReadSensors() error { 73 | // Get bytes from the sensor, packed into 6 bytes. 74 | bytes, e := n.device.Read(0, 6) 75 | if e != nil { 76 | return e 77 | } 78 | 79 | if len(bytes) < 6 { 80 | return fmt.Errorf("Error getting nunchuck data, expected 6 bytes but got %d", len(bytes)) 81 | } 82 | 83 | // Split out the packet into the n.last* variables. 84 | 85 | // bytes[0] and bytes[1] are joystick X and Y respectively 86 | n.lastJoyX = int(bytes[0]) 87 | n.lastJoyY = int(bytes[1]) 88 | 89 | // bytes[2] - bytes[4] are accelX, accelY and accelZ most significant byte respectively. LSB are in bytes[5] 90 | ax := int(int8(bytes[2]<<2)) | int((bytes[5]>>2)&3) 91 | ay := int(int8(bytes[3]<<2)) | int((bytes[5]>>4)&3) 92 | az := int(int8(bytes[4]<<2)) | int((bytes[5]>>6)&3) 93 | 94 | n.lastAccelX = float32(ax) - n.zeroAccelX 95 | n.lastAccelY = float32(ay) - n.zeroAccelY 96 | n.lastAccelZ = float32(az) - n.zeroAccelZ 97 | 98 | n.lastZPressed = true 99 | if bytes[5]&1 > 0 { 100 | n.lastZPressed = false 101 | } 102 | 103 | n.lastCPressed = true 104 | if bytes[5]&2 > 0 { 105 | n.lastCPressed = false 106 | } 107 | 108 | return nil 109 | } 110 | 111 | // Calibrate the joystick to the most recently read values. 112 | func (n *Nunchuck) CalibrateJoystick() { 113 | n.SetJoystickZero(n.lastJoyX, n.lastJoyY) 114 | } 115 | 116 | // Calibrate the joystick to explicit values. 117 | func (n *Nunchuck) SetJoystickZero(x int, y int) { 118 | n.zeroJoyX = x 119 | n.zeroJoyY = y 120 | } 121 | 122 | func (n *Nunchuck) SetAccelZero(x float32, y float32, z float32) { 123 | n.zeroAccelX = x 124 | n.zeroAccelY = y 125 | n.zeroAccelZ = z 126 | } 127 | 128 | func (n *Nunchuck) GetJoystick() (x int, y int) { 129 | return n.lastJoyX, n.lastJoyY 130 | } 131 | 132 | func (n *Nunchuck) GetAccel() (ax float32, ay float32, az float32) { 133 | return n.lastAccelX, n.lastAccelY, n.lastAccelZ 134 | } 135 | 136 | func (n *Nunchuck) GetZPressed() bool { 137 | return n.lastZPressed 138 | } 139 | 140 | func (n *Nunchuck) GetCPressed() bool { 141 | return n.lastCPressed 142 | } 143 | 144 | // Read roll in degrees, computed from accelerometer 145 | func (n *Nunchuck) GetRoll() float32 { 146 | return float32(math.Atan2(float64(n.lastAccelX), float64(n.lastAccelZ)) / math.Pi * 180.0) 147 | } 148 | 149 | // Read pitch in degrees, computed from accelerometer 150 | func (n *Nunchuck) GetPitch() float32 { 151 | return float32(math.Acos(float64(n.lastAccelY)/RADIUS) / math.Pi * 180.0) 152 | } 153 | -------------------------------------------------------------------------------- /devices/tmp102/README.md: -------------------------------------------------------------------------------- 1 | # TMP-102 I2C 2 | 3 | This provides a simple way to access the sensor value of a TMP-102 that is connected to an i2c bus on your system. 4 | 5 | # Usage 6 | 7 | Import the packages: 8 | 9 | // import the require modules 10 | import( 11 | "github.com/mrmorphic/hwio" 12 | "github.com/mrmorphic/hwio/devices/tmp102" 13 | ) 14 | 15 | Initialise by fetching an i2c module from the driver. You can get instances of devices attached to 16 | the bus. 17 | 18 | // Get the i2c module from the driver. This is an example for the Raspberry Pi. 19 | m, e := hwio.GetModule("i2c") 20 | 21 | // Assert that it is an I2C module 22 | i2c := m.(I2CModule) 23 | 24 | Get the TMP102 device, so you make requests of it: 25 | 26 | // Get a temp device on this i2c bus 27 | temp := tmp102.NewTMP102(i2c) 28 | 29 | Read values from the device whenever you want to: 30 | 31 | // Get the temperature sensor value 32 | t, e := temp.GetTemp() 33 | -------------------------------------------------------------------------------- /devices/tmp102/tmp102.go: -------------------------------------------------------------------------------- 1 | // Support for TMP-102 temperature sensor. 2 | 3 | // Current status: 4 | // - this driver is working as expected. 5 | // - it is being included as an example of how I2C devices can be added to the hwio package, and hopefully over time this 6 | // driver will be exended and more devices will be supported. 7 | 8 | package tmp102 9 | 10 | import ( 11 | "github.com/mrmorphic/hwio" 12 | ) 13 | 14 | const ( 15 | // This is the default address. 16 | DEVICE_ADDRESS = 0x48 17 | ) 18 | 19 | type TMP102 struct { 20 | device hwio.I2CDevice 21 | } 22 | 23 | func NewTMP102(module hwio.I2CModule) *TMP102 { 24 | device := module.GetDevice(DEVICE_ADDRESS) 25 | result := &TMP102{device: device} 26 | 27 | return result 28 | } 29 | 30 | func (t *TMP102) GetTemp() (float32, error) { 31 | buffer, e := t.device.Read(0x00, 2) 32 | if e != nil { 33 | return 0, e 34 | } 35 | MSB := buffer[0] 36 | LSB := buffer[1] 37 | 38 | /* Convert 12bit int using two's compliment */ 39 | /* Credit: http://bildr.org/2011/01/tmp102-arduino/ */ 40 | temp := ((int(MSB) << 8) | int(LSB)) >> 4 41 | 42 | // divide by 16, since lowest 4 bits are fractional. 43 | return float32(temp) * 0.0625, nil 44 | } 45 | -------------------------------------------------------------------------------- /driver_beagle_black.go: -------------------------------------------------------------------------------- 1 | package hwio 2 | 3 | import "strings" 4 | 5 | // A driver for BeagleBone's running Linux kernel 3.8 or higher, which use device trees instead 6 | // of the old driver. 7 | // 8 | // Notable differences between this driver and the other BeagleBone driver: 9 | // - this uses the file system for everything. 10 | // - will only work on linux kernel 3.8 and higher, irrespective of the board version. 11 | // - memory mapping is no longer used, as it was unsupported anyway. 12 | // - this will probably not have the raw performance of the memory map technique (this is yet to be measured) 13 | // - this driver will likely support alot more functions, as it's leveraging drivers that already exist. 14 | // 15 | // This driver shares some information from the other driver, since the pin configuration information is essentially the same. 16 | // 17 | // Articles used in building this driver: 18 | // GPIO: 19 | // - http://www.avrfreaks.net/wiki/index.php/Documentation:Linux/GPIO#Example_of_GPIO_access_from_within_a_C_program 20 | // Analog: 21 | // - http://hipstercircuits.com/reading-analog-adc-values-on-beaglebone-black/ 22 | // Background on changes in linux kernal 3.8: 23 | // - https://docs.google.com/document/d/17P54kZkZO_-JtTjrFuVz-Cp_RMMg7GB_8W9JK9sLKfA/edit?hl=en&forcehl=1#heading=h.mfjmczsbv38r 24 | 25 | // Notes on analog: 26 | // 27 | // echo cape-bone-iio > /sys/devices/bone_capemgr.*/slots ' once off 28 | // find /sys/ -name '*AIN*': 29 | // /sys/devices/ocp.2/helper.14/AIN0 30 | // /sys/devices/ocp.2/helper.14/AIN1 31 | // /sys/devices/ocp.2/helper.14/AIN2 32 | // /sys/devices/ocp.2/helper.14/AIN3 33 | // /sys/devices/ocp.2/helper.14/AIN4 34 | // /sys/devices/ocp.2/helper.14/AIN5 35 | // /sys/devices/ocp.2/helper.14/AIN6 36 | // /sys/devices/ocp.2/helper.14/AIN7 37 | 38 | type BeaglePin struct { 39 | names []string // This intended for the P8.16 format name (currently unused) 40 | modules []string // Names of modules that may allocate this pin 41 | 42 | gpioLogical int // logical number for GPIO, for pins used by "gpio" module. This is the GPIO port number plus the GPIO pin within the port. 43 | analogLogical int // analog pin number, for pins used by "analog" module 44 | } 45 | 46 | type BeagleBoneBlackDriver struct { 47 | // all pins understood by the driver 48 | beaglePins []*BeaglePin 49 | 50 | // a map of module names to module objects, created at initialisation 51 | modules map[string]Module 52 | } 53 | 54 | func NewBeagleboneBlackDTDriver() *BeagleBoneBlackDriver { 55 | return &BeagleBoneBlackDriver{} 56 | } 57 | 58 | // Examine the hardware environment and determine if this driver will handle it. 59 | // Note: this replaces the old detection which mostly used "uname -a" for BeagleBone 60 | // detection, but this was unreliable with too many edge cases. Now, it looks for 61 | // specific driver config. This becomes less based on disto etc and more based on 62 | // capability surfaced via device drivers. 63 | func (d *BeagleBoneBlackDriver) MatchesHardwareConfig() bool { 64 | // If bone_capemgr is not present, this driver won't work in any case 65 | path, e := findFirstMatchingFile("/sys/devices/bone_capemgr.*/slots") 66 | if e == nil && path != "" { 67 | return true 68 | } 69 | return false 70 | } 71 | 72 | func (d *BeagleBoneBlackDriver) Init() error { 73 | d.createPinData() 74 | return d.initialiseModules() 75 | } 76 | 77 | func (d *BeagleBoneBlackDriver) makePin(names []string, modules []string, gpioLogical int, analogLogical int) *BeaglePin { 78 | return &BeaglePin{names, modules, gpioLogical, analogLogical} 79 | } 80 | 81 | func (d *BeagleBoneBlackDriver) createPinData() { 82 | d.beaglePins = []*BeaglePin{ 83 | d.makePin([]string{"dummy"}, []string{"unassignable"}, 0, 0), // 0 - spacer 84 | // P8 85 | d.makePin([]string{"P8.3", "gpmc_ad6", "gpio1_6"}, []string{"gpio", "emmc2", "preallocated"}, 38, 0), // preassigned via DT in default config 86 | d.makePin([]string{"P8.4", "gpmc_ad7", "gpio1_7"}, []string{"gpio", "emmc2", "preallocated"}, 39, 0), // preassigned via DT in default config 87 | d.makePin([]string{"P8.5", "gpmc_ad2", "gpio1_2"}, []string{"gpio", "emmc2", "preallocated"}, 34, 0), // preassigned via DT in default config 88 | d.makePin([]string{"P8.6", "gpmc_ad3", "gpio1_3"}, []string{"gpio", "emmc2", "preallocated"}, 35, 0), // preassigned via DT in default config 89 | d.makePin([]string{"P8.7", "gpmc_advn_ale", "gpio2_2"}, []string{"gpio"}, 66, 0), 90 | d.makePin([]string{"P8.8", "gpmc_oen_ren", "gpio2_3"}, []string{"gpio"}, 67, 0), 91 | d.makePin([]string{"P8.9", "gpmc_ben0_cle", "gpio2_5"}, []string{"gpio"}, 69, 0), 92 | d.makePin([]string{"P8.10", "gpmc_wen", "gpio2_4"}, []string{"gpio"}, 68, 0), 93 | d.makePin([]string{"P8.11", "gpmc_ad13", "gpio1_13"}, []string{"gpio"}, 45, 0), 94 | d.makePin([]string{"P8.12", "gpmc_ad12", "gpio1_12"}, []string{"gpio"}, 44, 0), 95 | d.makePin([]string{"P8.13", "gpmc_ad9", "gpio0_23", "ehrpwm2B"}, []string{"gpio", "pwm2"}, 23, 0), 96 | d.makePin([]string{"P8.14", "gpmc_ad10", "gpio0_26"}, []string{"gpio"}, 26, 0), 97 | d.makePin([]string{"P8.15", "gpmc_ad15", "gpio1_15"}, []string{"gpio"}, 47, 0), 98 | d.makePin([]string{"P8.16", "gpmc_ad14", "gpio1_14"}, []string{"gpio"}, 46, 0), 99 | d.makePin([]string{"P8.17", "gpmc_ad11", "gpio0_27"}, []string{"gpio"}, 27, 0), 100 | d.makePin([]string{"P8.18", "gpmc_clk", "gpio2_1"}, []string{"gpio"}, 65, 0), 101 | d.makePin([]string{"P8.19", "gpmc_ad8", "gpio0_22", "ehrpwm2A"}, []string{"gpio", "pwm2"}, 22, 0), 102 | d.makePin([]string{"P8.20", "gpmc_csn2", "gpio1_31"}, []string{"gpio", "emmc2", "preallocated"}, 63, 0), // preassigned via DT in default config 103 | d.makePin([]string{"P8.21", "gpmc_csn1", "gpio1_30"}, []string{"gpio", "emmc2", "preallocated"}, 62, 0), // preassigned via DT in default config 104 | d.makePin([]string{"P8.22", "gpmc_ad5", "gpio1_5"}, []string{"gpio", "emmc2", "preallocated"}, 37, 0), // preassigned via DT in default config 105 | d.makePin([]string{"P8.23", "gpmc_ad4", "gpio1_4"}, []string{"gpio", "emmc2", "preallocated"}, 36, 0), // preassigned via DT in default config 106 | d.makePin([]string{"P8.24", "gpmc_ad1", "gpio1_1"}, []string{"gpio", "emmc2", "preallocated"}, 33, 0), // preassigned via DT in default config 107 | d.makePin([]string{"P8.25", "gpmc_ad0", "gpio1_0"}, []string{"gpio", "emmc2", "preallocated"}, 32, 0), // preassigned via DT in default config 108 | d.makePin([]string{"P8.26", "gpmc_csn0", "gpio1_29"}, []string{"gpio"}, 61, 0), 109 | d.makePin([]string{"P8.27", "lcd_vsync", "gpio2_22"}, []string{"gpio", "hdmi", "preallocated"}, 86, 0), // preassigned via DT in default config 110 | d.makePin([]string{"P8.28", "lcd_pclk", "gpio2_24"}, []string{"gpio", "hdmi", "preallocated"}, 88, 0), // preassigned via DT in default config 111 | d.makePin([]string{"P8.29", "lcd_hsync", "gpio2_23"}, []string{"gpio", "hdmi", "preallocated"}, 87, 0), // preassigned via DT in default config 112 | d.makePin([]string{"P8.30", "lcd_ac_bias_en", "gpio2_25"}, []string{"gpio", "hdmi", "preallocated"}, 89, 0), // preassigned via DT in default config 113 | d.makePin([]string{"P8.31", "lcd_data14", "gpio0_10"}, []string{"gpio", "hdmi", "preallocated"}, 10, 0), // preassigned via DT in default config 114 | d.makePin([]string{"P8.32", "lcd_data15", "gpio0_11"}, []string{"gpio", "hdmi", "preallocated"}, 11, 0), // preassigned via DT in default config 115 | d.makePin([]string{"P8.33", "lcd_data13", "gpio0_9"}, []string{"gpio", "hdmi", "preallocated"}, 9, 0), // preassigned via DT in default config 116 | d.makePin([]string{"P8.34", "lcd_data11", "gpio2_17"}, []string{"gpio", "hdmi", "pwm1", "preallocated"}, 81, 0), // preassigned via DT in default config 117 | d.makePin([]string{"P8.35", "lcd_data12", "gpio0_8"}, []string{"gpio", "hdmi", "preallocated"}, 8, 0), // preassigned via DT in default config 118 | d.makePin([]string{"P8.36", "lcd_data10", "gpio2_16"}, []string{"gpio", "hdmi", "pwm1", "preallocated"}, 80, 0), // preassigned via DT in default config 119 | d.makePin([]string{"P8.37", "lcd_data8", "gpio2_14"}, []string{"gpio", "hdmi", "preallocated"}, 78, 0), // preassigned via DT in default config 120 | d.makePin([]string{"P8.38", "lcd_data9", "gpio2_15"}, []string{"gpio", "hdmi", "preallocated"}, 79, 0), // preassigned via DT in default config 121 | d.makePin([]string{"P8.40", "lcd_data7", "gpio2_13"}, []string{"gpio", "hdmi", "preallocated"}, 77, 0), // preassigned via DT in default config 122 | d.makePin([]string{"P8.41", "lcd_data4", "gpio2_10"}, []string{"gpio", "hdmi", "preallocated"}, 74, 0), // preassigned via DT in default config 123 | d.makePin([]string{"P8.42", "lcd_data5", "gpio2_11"}, []string{"gpio", "hdmi", "preallocated"}, 75, 0), // preassigned via DT in default config 124 | d.makePin([]string{"P8.43", "lcd_data2", "gpio2_8"}, []string{"gpio", "hdmi", "preallocated"}, 72, 0), // preassigned via DT in default config 125 | d.makePin([]string{"P8.44", "lcd_data3", "gpio2_9"}, []string{"gpio", "hdmi", "pwm2", "preallocated"}, 73, 0), // preassigned via DT in default config 126 | d.makePin([]string{"P8.45", "lcd_data0", "gpio2_6"}, []string{"gpio", "hdmi", "pwm2", "preallocated"}, 70, 0), // preassigned via DT in default config 127 | // makePin("P8.46", bbGpioProfile, "gpio2_7", 2, 7, "lcd_data1", 0), 128 | 129 | // P9 130 | d.makePin([]string{"P9.11", "gpmc_wait0", "gpio0_30"}, []string{"gpio"}, 30, 0), 131 | d.makePin([]string{"P9.12", "gpmc_ben1", "gpio1_28"}, []string{"gpio"}, 60, 0), 132 | d.makePin([]string{"P9.13", "gpmc_wpn", "gpio0_31"}, []string{"gpio"}, 31, 0), 133 | d.makePin([]string{"P9.14", "gpmc_a2", "gpio1_18"}, []string{"gpio"}, 50, 0), 134 | d.makePin([]string{"P9.15", "gpmc_a0", "gpio1_16"}, []string{"gpio"}, 48, 0), 135 | d.makePin([]string{"P9.16", "gpmc_a3", "gpio1_19"}, []string{"gpio"}, 51, 0), 136 | d.makePin([]string{"P9.17", "spi0_cs0", "gpio0_5"}, []string{"gpio"}, 5, 0), 137 | d.makePin([]string{"P9.18", "spi0_d1", "gpio0_4"}, []string{"gpio"}, 4, 0), 138 | d.makePin([]string{"P9.19", "uart1_rtsn", "gpio0_13"}, []string{"gpio", "i2c2"}, 13, 0), // preassigned via DT in default config 139 | d.makePin([]string{"P9.20", "uart1_ctsn", "gpio0_12"}, []string{"gpio", "i2c2"}, 12, 0), // preassigned via DT in default config 140 | d.makePin([]string{"P9.21", "spi0_d0", "gpio0_3", "ehrpwm0B"}, []string{"gpio", "pwm0"}, 3, 0), 141 | d.makePin([]string{"P9.22", "spi0_sclk", "gpio0_2", "ehrpwm0A"}, []string{"gpio", "pwm0"}, 2, 0), 142 | d.makePin([]string{"P9.23", "gpmc_a1", "gpio1_17"}, []string{"gpio"}, 49, 0), 143 | d.makePin([]string{"P9.24", "uart1_txd", "gpio0_15"}, []string{"gpio"}, 15, 0), 144 | d.makePin([]string{"P9.25", "mcasp0_ahclkx", "gpio3_21"}, []string{"gpio", "mcasp0", "preallocated"}, 117, 0), // preassigned via DT in default config 145 | d.makePin([]string{"P9.26", "uart1_rxd", "gpio0_14"}, []string{"gpio"}, 14, 0), 146 | d.makePin([]string{"P9.27", "mcasp0_fsr", "gpio3_19"}, []string{"gpio"}, 115, 0), 147 | d.makePin([]string{"P9.28", "mcasp0_ahclkr", "gpio3_17"}, []string{"gpio", "mcasp0", "preallocated"}, 113, 0), // preassigned via DT in default config 148 | d.makePin([]string{"P9.29", "mcasp0_fsx", "gpio3_15"}, []string{"gpio", "mcasp0", "pwm0", "preallocated"}, 111, 0), // preassigned via DT in default config 149 | d.makePin([]string{"P9.30", "mcasp0_axr0", "gpio3_16"}, []string{"gpio"}, 112, 0), 150 | d.makePin([]string{"P9.31", "mcasp0_aclkx", "gpio3_14"}, []string{"gpio", "mcasp0", "pwm0", "preallocated"}, 110, 0), // preassigned via DT in default config 151 | d.makePin([]string{"P9.33", "ain4"}, []string{"analog"}, 0, 4), 152 | d.makePin([]string{"P9.35", "ain6"}, []string{"analog"}, 0, 6), 153 | d.makePin([]string{"P9.36", "ain5"}, []string{"analog"}, 0, 5), 154 | d.makePin([]string{"P9.37", "ain2"}, []string{"analog"}, 0, 2), 155 | d.makePin([]string{"P9.38", "ain3"}, []string{"analog"}, 0, 3), 156 | d.makePin([]string{"P9.39", "ain0"}, []string{"analog"}, 0, 0), 157 | d.makePin([]string{"P9.40", "ain1"}, []string{"analog"}, 0, 1), 158 | d.makePin([]string{"P9.41", "xdma_event_intr1", "gpio0_20"}, []string{"gpio"}, 20, 0), 159 | d.makePin([]string{"P9.42", "ecap0_in_pwm0_out", "gpio0_7"}, []string{"gpio"}, 7, 0), 160 | } 161 | } 162 | 163 | func (d *BeagleBoneBlackDriver) initialiseModules() error { 164 | d.modules = make(map[string]Module) 165 | 166 | gpio := NewDTGPIOModule("gpio") 167 | e := gpio.SetOptions(d.getGPIOOptions()) 168 | if e != nil { 169 | return e 170 | } 171 | 172 | analog := NewBBAnalogModule("analog") 173 | e = analog.SetOptions(d.getAnalogOptions()) 174 | if e != nil { 175 | return e 176 | } 177 | 178 | i2c2 := NewDTI2CModule("i2c2") 179 | e = i2c2.SetOptions(d.getI2C2Options()) 180 | if e != nil { 181 | return e 182 | } 183 | 184 | preallocated := NewPreassignedModule("preallocated") 185 | e = preallocated.SetOptions(d.getPreallocatedOptions()) 186 | if e != nil { 187 | return e 188 | } 189 | 190 | pwm0 := NewBBPWMModule("pwm0") 191 | e = pwm0.SetOptions(d.getPWMOptions("pwm0")) 192 | if e != nil { 193 | return e 194 | } 195 | 196 | pwm1 := NewBBPWMModule("pwm1") 197 | e = pwm1.SetOptions(d.getPWMOptions("pwm1")) 198 | if e != nil { 199 | return e 200 | } 201 | pwm2 := NewBBPWMModule("pwm2") 202 | e = pwm2.SetOptions(d.getPWMOptions("pwm2")) 203 | if e != nil { 204 | return e 205 | } 206 | 207 | // Create the leds module which is BBB-specific. There are no options. 208 | leds := NewDTLEDModule("leds") 209 | e = leds.SetOptions(d.getLEDOptions("leds")) 210 | if e != nil { 211 | return e 212 | } 213 | 214 | d.modules["gpio"] = gpio 215 | d.modules["analog"] = analog 216 | d.modules["i2c2"] = i2c2 217 | d.modules["pwm0"] = pwm0 218 | d.modules["pwm1"] = pwm1 219 | d.modules["pwm2"] = pwm2 220 | d.modules["leds"] = leds 221 | 222 | // alias i2c to i2c2. This is for portability; getting the i2c module on any device should return the default i2c interface, 223 | // but should not preclude addition of other i2c busses. 224 | d.modules["i2c"] = i2c2 225 | 226 | // these are the pre-allocated pins 227 | d.modules["preallocated"] = preallocated 228 | 229 | // initialise by default, which will assign P9.19 and P9.20. This is configured by default in device tree and these pins cannot be assigned. 230 | i2c2.Enable() 231 | preallocated.Enable() 232 | 233 | return nil 234 | } 235 | 236 | // Get options for GPIO module, derived from the pin structure 237 | func (d *BeagleBoneBlackDriver) getGPIOOptions() map[string]interface{} { 238 | result := make(map[string]interface{}) 239 | 240 | pins := make(DTGPIOModulePinDefMap) 241 | 242 | // Add the GPIO pins to this map 243 | for i, hw := range d.beaglePins { 244 | if d.usedBy(hw, "gpio") { 245 | pins[Pin(i)] = &DTGPIOModulePinDef{pin: Pin(i), gpioLogical: hw.gpioLogical} 246 | } 247 | } 248 | result["pins"] = pins 249 | 250 | return result 251 | } 252 | 253 | // Get options for analog module, derived from the pin structure 254 | func (d *BeagleBoneBlackDriver) getAnalogOptions() map[string]interface{} { 255 | result := make(map[string]interface{}) 256 | 257 | pins := make(BBAnalogModulePinDefMap) 258 | 259 | // Add the GPIO pins to this map 260 | for i, hw := range d.beaglePins { 261 | if d.usedBy(hw, "analog") { 262 | pins[Pin(i)] = &BBAnalogModulePinDef{pin: Pin(i), analogLogical: hw.analogLogical} 263 | } 264 | } 265 | result["pins"] = pins 266 | 267 | return result 268 | } 269 | 270 | // Return the i2c options required to initialise that module. 271 | func (d *BeagleBoneBlackDriver) getI2C2Options() map[string]interface{} { 272 | result := make(map[string]interface{}) 273 | 274 | pins := make(DTI2CModulePins, 0) 275 | p19 := d.getPin("P9.19") 276 | pins = append(pins, p19) 277 | p20 := d.getPin("P9.20") 278 | pins = append(pins, p20) 279 | 280 | result["pins"] = pins 281 | 282 | // this should really look at the device structure to ensure that I2C2 on hardware maps to /dev/i2c1. This confusion seems 283 | // to happen because of the way the kernel initialises the devices at boot time. 284 | result["device"] = "/dev/i2c-1" 285 | 286 | return result 287 | } 288 | 289 | func (d *BeagleBoneBlackDriver) getPWMOptions(name string) map[string]interface{} { 290 | result := make(map[string]interface{}) 291 | 292 | pins := make(BBPWMModulePinDefMap, 0) 293 | for i, hw := range d.beaglePins { 294 | if d.usedBy(hw, name) { 295 | n := hw.names[0] 296 | n = strings.Replace(n, ".", "_", -1) // P8.13 => P8_13 297 | pins[Pin(i)] = &BBPWMModulePinDef{pin: Pin(i), name: n} 298 | } 299 | } 300 | 301 | result["pins"] = pins 302 | 303 | return result 304 | } 305 | 306 | func (d *BeagleBoneBlackDriver) getLEDOptions(name string) map[string]interface{} { 307 | result := make(map[string]interface{}) 308 | 309 | pins := make(DTLEDModulePins) 310 | pins["usr0"] = "/sys/class/leds/beaglebone:green:usr0/" 311 | pins["usr1"] = "/sys/class/leds/beaglebone:green:usr1/" 312 | pins["usr2"] = "/sys/class/leds/beaglebone:green:usr2/" 313 | pins["usr3"] = "/sys/class/leds/beaglebone:green:usr3/" 314 | 315 | result["pins"] = pins 316 | 317 | return result 318 | } 319 | 320 | // internal function to get a Pin. It does not use GetPin because that relies on the driver having already been initialised. This 321 | // method can be called while stil initialising. Only matches names[0], which is the Pn.nn expansion header name. 322 | func (d *BeagleBoneBlackDriver) getPin(name string) Pin { 323 | for i, hw := range d.beaglePins { 324 | if hw.names[0] == name { 325 | return Pin(i) 326 | } 327 | } 328 | return Pin(0) 329 | } 330 | 331 | func (d *BeagleBoneBlackDriver) getPreallocatedOptions() map[string]interface{} { 332 | result := make(map[string]interface{}) 333 | 334 | pins := make(PinList, 0) 335 | 336 | // Add all pre-allocated pins to this map (excludes pre-allocated pins picked up by other modules, eg i2c2.) 337 | for i, hw := range d.beaglePins { 338 | if d.usedBy(hw, "preallocated") { 339 | pins = append(pins, Pin(i)) 340 | } 341 | } 342 | 343 | result["pins"] = pins 344 | 345 | return result 346 | } 347 | 348 | // Determine if the pin is used by the module 349 | func (d *BeagleBoneBlackDriver) usedBy(pinDef *BeaglePin, module string) bool { 350 | for _, n := range pinDef.modules { 351 | if n == module { 352 | return true 353 | } 354 | } 355 | return false 356 | } 357 | 358 | func (d *BeagleBoneBlackDriver) GetModules() map[string]Module { 359 | return d.modules 360 | } 361 | 362 | func (d *BeagleBoneBlackDriver) Close() { 363 | // Disable all the modules 364 | for _, module := range d.modules { 365 | module.Disable() 366 | } 367 | } 368 | 369 | func (d *BeagleBoneBlackDriver) PinMap() (pinMap HardwarePinMap) { 370 | pinMap = make(HardwarePinMap) 371 | 372 | for i, hw := range d.beaglePins { 373 | pinMap.Add(Pin(i), hw.names, hw.modules) 374 | } 375 | 376 | return 377 | } 378 | -------------------------------------------------------------------------------- /driver_mock.go: -------------------------------------------------------------------------------- 1 | package hwio 2 | 3 | // A mock driver used for unit testing. 4 | import ( 5 | // "errors" 6 | "fmt" 7 | ) 8 | 9 | type testDriverPin struct { 10 | names []string 11 | modules []string 12 | extra string 13 | } 14 | 15 | type testDriverPinMap map[Pin]*testDriverPin 16 | 17 | type TestDriver struct { 18 | pinDefs []*testDriverPin 19 | modules map[string]Module 20 | 21 | verbose bool 22 | } 23 | 24 | func (d *TestDriver) Init() error { 25 | d.createPinData() 26 | d.initialiseModules() 27 | 28 | return nil 29 | } 30 | 31 | func (d *TestDriver) Close() { 32 | 33 | } 34 | 35 | func (d *TestDriver) MatchesHardwareConfig() bool { 36 | return true 37 | } 38 | 39 | // func (d *TestDriver) SetVerbosity(verbose bool) { 40 | // d.verbose = verbose 41 | // } 42 | 43 | // // Mock records the pin mode being assigned. 44 | // func (d *TestDriver) PinMode(pin Pin, mode PinIOMode) (e error) { 45 | // if d.verbose { 46 | // fmt.Printf("PinMode(%d, %s)\n", pin, mode.String()) 47 | // } 48 | // m := d.getPinModes() 49 | // m[pin] = mode 50 | // return nil 51 | // } 52 | 53 | // // Mock emulates DigitalWrite by writing the value to pinValues. 54 | // func (d *TestDriver) DigitalWrite(pin Pin, value int) (e error) { 55 | // if d.verbose { 56 | // fmt.Printf("DigitalWrite(%d, %d)\n", pin, value) 57 | // } 58 | // m := d.getPinValues() 59 | // m[pin] = value 60 | // return nil 61 | // } 62 | 63 | // func (d *TestDriver) DigitalRead(pin Pin) (value int, e error) { 64 | // m := d.getPinValues() 65 | // return m[pin], nil 66 | // } 67 | 68 | // func (d *TestDriver) AnalogWrite(pin Pin, value int) (e error) { 69 | // // just the same as DigitalWrite, store value in the pinValues map 70 | // return DigitalWrite(pin, value) 71 | // } 72 | 73 | // func (d *TestDriver) AnalogRead(pin Pin) (value int, e error) { 74 | // if pin == 6 { 75 | // return 1, nil 76 | // } 77 | // if pin == 7 { 78 | // return 1000, nil 79 | // } 80 | // return 0, errors.New("analog read got error") 81 | // } 82 | 83 | // // Mock has a fixed set of hardcoded pins with different capabilities 84 | func (d *TestDriver) PinMap() HardwarePinMap { 85 | result := make(HardwarePinMap) 86 | 87 | for i, hw := range d.pinDefs { 88 | result.Add(Pin(i), hw.names, hw.modules) 89 | } 90 | 91 | return result 92 | } 93 | 94 | // // Getter that gets the pinModes map on demand, and creating it on first 95 | // // instance. 96 | // func (d *TestDriver) getPinModes() map[Pin]PinIOMode { 97 | // if d.pinModes == nil { 98 | // d.pinModes = make(map[Pin]PinIOMode) 99 | // } 100 | // return d.pinModes 101 | // } 102 | 103 | // func (d *TestDriver) getPinValues() map[Pin]int { 104 | // if d.pinValues == nil { 105 | // d.pinValues = make(map[Pin]int) 106 | // } 107 | // return d.pinValues 108 | // } 109 | 110 | // func (d *TestDriver) MockGetPinMode(pin Pin) PinIOMode { 111 | // m := d.getPinModes() 112 | // return m[pin] 113 | // } 114 | 115 | // func (d *TestDriver) MockGetPinValue(pin Pin) int { 116 | // m := d.getPinValues() 117 | // return m[pin] 118 | // } 119 | 120 | // func (d *TestDriver) MockSetPinValue(pin Pin, value int) { 121 | // m := d.getPinValues() 122 | // m[pin] = value 123 | // } 124 | 125 | func (d *TestDriver) GetModules() map[string]Module { 126 | return d.modules 127 | } 128 | 129 | func (d *TestDriver) createPinData() { 130 | d.pinDefs = []*testDriverPin{ 131 | d.makeTestPin([]string{"P1", "gpio1"}, []string{"gpio"}, "1"), 132 | d.makeTestPin([]string{"P2", "gpio2"}, []string{"gpio"}, "2"), 133 | d.makeTestPin([]string{"P3", "gpio3"}, []string{"gpio"}, "3"), 134 | d.makeTestPin([]string{"P4", "gpio4"}, []string{"gpio"}, "4"), 135 | d.makeTestPin([]string{"P5", "gpio5"}, []string{"gpio"}, "5"), 136 | d.makeTestPin([]string{"P6", "gpio6"}, []string{"gpio"}, "6"), 137 | d.makeTestPin([]string{"P7", "gpio7"}, []string{"gpio"}, "7"), 138 | d.makeTestPin([]string{"P8", "gpio8"}, []string{"gpio"}, "8"), 139 | d.makeTestPin([]string{"P9", "gpio9"}, []string{"gpio"}, "9"), 140 | d.makeTestPin([]string{"P10", "gpio10"}, []string{"gpio"}, "10"), 141 | d.makeTestPin([]string{"P11", "ain4"}, []string{"analog"}, "11"), 142 | d.makeTestPin([]string{"P12", "ain6"}, []string{"analog"}, "12"), 143 | } 144 | } 145 | 146 | func (d *TestDriver) makeTestPin(names []string, modules []string, extra string) *testDriverPin { 147 | result := &testDriverPin{names, modules, extra} 148 | return result 149 | } 150 | 151 | func (d *TestDriver) initialiseModules() { 152 | d.modules = make(map[string]Module) 153 | 154 | gpio := newTestGPIOModule("gpio") 155 | gpio.SetOptions(d.getModuleOptions("gpio")) 156 | 157 | analog := newTestAnalogModule("analog") 158 | analog.SetOptions(d.getModuleOptions("analog")) 159 | 160 | // i2c1 := NewDTI2CModule("i2c1") 161 | 162 | d.modules["gpio"] = gpio 163 | d.modules["analog"] = analog 164 | // d.modules["i2c1"] = i2c1 165 | } 166 | 167 | func (d *TestDriver) getModuleOptions(module string) map[string]interface{} { 168 | result := make(map[string]interface{}) 169 | 170 | pins := make(testDriverPinMap) 171 | 172 | for i, hw := range d.pinDefs { 173 | if hw.modules[0] == module { 174 | pins[Pin(i)] = hw 175 | } 176 | } 177 | result["pins"] = pins 178 | 179 | return result 180 | } 181 | 182 | // Mock module to replicate GPIO behaviour 183 | type testGPIOModule struct { 184 | name string 185 | 186 | pinDefs testDriverPinMap 187 | 188 | pinModes map[Pin]PinIOMode 189 | 190 | // this simulates actual pin values. DigitalWrite ends up settin 191 | pinValues map[Pin]int 192 | } 193 | 194 | func newTestGPIOModule(name string) *testGPIOModule { 195 | result := &testGPIOModule{name: name} 196 | result.pinModes = make(map[Pin]PinIOMode) 197 | result.pinValues = make(map[Pin]int) 198 | return result 199 | } 200 | 201 | func (module *testGPIOModule) SetOptions(map[string]interface{}) error { 202 | return nil 203 | } 204 | 205 | func (module *testGPIOModule) Enable() error { 206 | return nil 207 | } 208 | 209 | func (module *testGPIOModule) Disable() error { 210 | return nil 211 | } 212 | 213 | func (module *testGPIOModule) GetName() string { 214 | return module.name 215 | } 216 | 217 | func (module *testGPIOModule) PinMode(pin Pin, mode PinIOMode) error { 218 | module.pinModes[pin] = mode 219 | return nil 220 | } 221 | 222 | func (module *testGPIOModule) DigitalWrite(pin Pin, value int) error { 223 | if module.pinModes[pin] == 0 { 224 | return fmt.Errorf("Pin %d has not had mode set", pin) 225 | } 226 | module.pinValues[pin] = value 227 | return nil 228 | } 229 | 230 | func (module *testGPIOModule) DigitalRead(pin Pin) (int, error) { 231 | return module.pinValues[pin], nil 232 | 233 | } 234 | 235 | func (module *testGPIOModule) ClosePin(pin Pin) error { 236 | return nil 237 | } 238 | 239 | func (module *testGPIOModule) MockGetPinMode(pin Pin) PinIOMode { 240 | return module.pinModes[pin] 241 | } 242 | 243 | func (module *testGPIOModule) MockGetPinValue(pin Pin) int { 244 | return module.pinValues[pin] 245 | } 246 | 247 | func (module *testGPIOModule) MockSetPinValue(pin Pin, value int) { 248 | module.pinValues[pin] = value 249 | } 250 | 251 | // Mock module to replicate analog module behaviour. 252 | type testAnalogModule struct { 253 | name string 254 | 255 | pinDefs testDriverPinMap 256 | } 257 | 258 | func newTestAnalogModule(name string) *testAnalogModule { 259 | return &testAnalogModule{name: name} 260 | } 261 | 262 | func (module *testAnalogModule) SetOptions(map[string]interface{}) error { 263 | return nil 264 | } 265 | 266 | func (module *testAnalogModule) Enable() error { 267 | return nil 268 | } 269 | 270 | func (module *testAnalogModule) Disable() error { 271 | return nil 272 | } 273 | 274 | func (module *testAnalogModule) GetName() string { 275 | return module.name 276 | } 277 | 278 | func (module *testAnalogModule) AnalogRead(pin Pin) (result int, e error) { 279 | if pin == 10 { 280 | return 1, nil 281 | } 282 | if pin == 11 { 283 | return 1000, nil 284 | } 285 | return 0, nil 286 | } 287 | -------------------------------------------------------------------------------- /driver_odroid_c1.go: -------------------------------------------------------------------------------- 1 | package hwio 2 | 3 | // A driver for Odroid C1's running Ubuntu 14.04 with Linux kernel 3.8 or higher. 4 | // 5 | // Known issues: 6 | // - INPUT_PULLUP and INPUT_PULLDOWN not implemented yet. 7 | // - no support yet for SPI, serial, I2C 8 | // 9 | // GPIO are 3.3V, analog is 1.8V 10 | // 11 | // Articles used in building this driver: 12 | // - http://www.hardkernel.com/main/products/prdt_info.php?g_code=G141578608433&tab_idx=2 13 | 14 | type OdroidC1Driver struct { 15 | // all pins understood by the driver 16 | pinConfigs []*DTPinConfig 17 | 18 | // a map of module names to module objects, created at initialisation 19 | modules map[string]Module 20 | } 21 | 22 | func NewOdroidC1Driver() *OdroidC1Driver { 23 | return &OdroidC1Driver{} 24 | } 25 | 26 | // Examine the hardware environment and determine if this driver will handle it. 27 | // For Odroid C1, it's easy: /proc/cpuinfo identifies it. 28 | func (d *OdroidC1Driver) MatchesHardwareConfig() bool { 29 | // we need to get CPU 3, because /proc/cpuinfo on odroid has a set of properties 30 | // that are system wide, that are listed after CPU specific properties. 31 | // CpuInfo associated these with CPU 3, the last one it saw. Not ideal, but works. 32 | hw := CpuInfo(3, "Hardware") 33 | if hw == "ODROIDC" { 34 | return true 35 | } 36 | return false 37 | } 38 | 39 | func (d *OdroidC1Driver) Init() error { 40 | d.createPinData() 41 | return d.initialiseModules() 42 | } 43 | 44 | func (d *OdroidC1Driver) createPinData() { 45 | d.pinConfigs = []*DTPinConfig{ 46 | // dummy placeholder for "pin 0" 47 | &DTPinConfig{[]string{"dummy"}, []string{"unassignable"}, 0, 0}, // 0 - spacer 48 | 49 | // Odroid has a mostly Raspberry Pi compatible header (40-pin), except GPIO numbers are different, 50 | // and an analog input is available. 51 | &DTPinConfig{[]string{"3.3v-1"}, []string{"unassignable"}, 0, 0}, // 1 52 | &DTPinConfig{[]string{"5v-1"}, []string{"unassignable"}, 0, 0}, // 2 53 | &DTPinConfig{[]string{"sda1"}, []string{"i2ca"}, 0, 0}, // 3 54 | &DTPinConfig{[]string{"5v-2"}, []string{"unassignable"}, 0, 0}, // 4 55 | &DTPinConfig{[]string{"scl1"}, []string{"i2ca"}, 0, 0}, // 5 56 | &DTPinConfig{[]string{"ground-1"}, []string{"unassignable"}, 0, 0}, // 6 57 | &DTPinConfig{[]string{"gpio83"}, []string{"gpio"}, 83, 0}, // 7 58 | &DTPinConfig{[]string{"txd"}, []string{"serial"}, 0, 0}, // 8 59 | &DTPinConfig{[]string{"ground-2"}, []string{"unassignable"}, 0, 0}, // 9 60 | &DTPinConfig{[]string{"rxd"}, []string{"serial"}, 0, 0}, // 10 61 | &DTPinConfig{[]string{"gpio88"}, []string{"gpio"}, 88, 0}, // 11 62 | &DTPinConfig{[]string{"gpio87"}, []string{"gpio"}, 87, 0}, // 12 63 | &DTPinConfig{[]string{"gpio116"}, []string{"gpio"}, 116, 0}, // 13 64 | &DTPinConfig{[]string{"ground-3"}, []string{"unassignable"}, 0, 0}, // 14 65 | &DTPinConfig{[]string{"gpio115"}, []string{"gpio"}, 115, 0}, // 15 66 | &DTPinConfig{[]string{"gpio104"}, []string{"gpio"}, 104, 0}, // 16 67 | &DTPinConfig{[]string{"3.3v-2"}, []string{"unassignable"}, 0, 0}, // 17 68 | &DTPinConfig{[]string{"gpio102"}, []string{"gpio"}, 102, 0}, // 18 69 | &DTPinConfig{[]string{"mosi"}, []string{"spi"}, 0, 0}, // 19 - may be GPIO by default - CHECK 70 | &DTPinConfig{[]string{"ground-4"}, []string{"unassignable"}, 0, 0}, // 20 71 | &DTPinConfig{[]string{"miso"}, []string{"spi"}, 0, 0}, // 21 - may be GPIO by default - CHECK 72 | &DTPinConfig{[]string{"gpio103"}, []string{"gpio"}, 103, 0}, // 22 73 | &DTPinConfig{[]string{"sclk"}, []string{"spi"}, 0, 0}, // 23 - may be GPIO by default - CHECK 74 | &DTPinConfig{[]string{"ce0"}, []string{"spi"}, 0, 0}, // 24 - also marked as CE0 75 | &DTPinConfig{[]string{"ground-5"}, []string{"unassignable"}, 0, 0}, // 25 76 | &DTPinConfig{[]string{"gpio118"}, []string{"gpio"}, 118, 0}, // 26 77 | &DTPinConfig{[]string{"sda2"}, []string{"i2cb"}, 0, 0}, // 27 78 | &DTPinConfig{[]string{"scl2"}, []string{"i2cb"}, 0, 0}, // 28 79 | &DTPinConfig{[]string{"gpio101"}, []string{"gpio"}, 101, 0}, // 29 80 | &DTPinConfig{[]string{"ground-6"}, []string{"unassignable"}, 0, 0}, // 30 81 | &DTPinConfig{[]string{"gpio100"}, []string{"gpio"}, 100, 0}, // 31 82 | &DTPinConfig{[]string{"gpio99"}, []string{"gpio"}, 99, 0}, // 32 83 | &DTPinConfig{[]string{"gpio108"}, []string{"gpio"}, 108, 0}, // 33 84 | &DTPinConfig{[]string{"ground-7"}, []string{"unassignable"}, 0, 0}, // 34 85 | &DTPinConfig{[]string{"gpio97"}, []string{"gpio"}, 97, 0}, // 35 86 | &DTPinConfig{[]string{"gpio98"}, []string{"gpio"}, 98, 0}, // 36 87 | &DTPinConfig{[]string{"ain1"}, []string{"analog"}, 26, 1}, // 37 - different from Rpi 88 | &DTPinConfig{[]string{"1.8v"}, []string{"unassignable"}, 0, 0}, // 38 - different from Rpi 89 | &DTPinConfig{[]string{"ground-8"}, []string{"unassignable"}, 0, 0}, // 39 - different from Rpi 90 | &DTPinConfig{[]string{"ain0"}, []string{"analog"}, 21, 0}, // 40 - different from Rpi 91 | } 92 | } 93 | 94 | func (d *OdroidC1Driver) initialiseModules() error { 95 | d.modules = make(map[string]Module) 96 | 97 | gpio := NewDTGPIOModule("gpio") 98 | e := gpio.SetOptions(d.getGPIOOptions()) 99 | if e != nil { 100 | return e 101 | } 102 | 103 | analog := NewODroidC1AnalogModule("analog") 104 | e = analog.SetOptions(d.getAnalogOptions()) 105 | if e != nil { 106 | return e 107 | } 108 | 109 | i2ca := NewDTI2CModule("i2ca") 110 | e = i2ca.SetOptions(d.getI2COptions("i2ca")) 111 | if e != nil { 112 | return e 113 | } 114 | i2cb := NewDTI2CModule("i2cb") 115 | e = i2cb.SetOptions(d.getI2COptions("i2cb")) 116 | if e != nil { 117 | return e 118 | } 119 | 120 | d.modules["gpio"] = gpio 121 | d.modules["analog"] = analog 122 | d.modules["i2ca"] = i2ca 123 | d.modules["i2cb"] = i2cb 124 | 125 | // alias i2c to i2c2. This is for portability; getting the i2c module on any device should return the default i2c interface, 126 | // but should not preclude addition of other i2c busses. 127 | d.modules["i2c"] = i2ca 128 | 129 | // initialise by default, which will assign P9.19 and P9.20. This is configured by default in device tree and these pins cannot be assigned. 130 | i2ca.Enable() 131 | i2cb.Enable() 132 | analog.Enable() 133 | 134 | return nil 135 | } 136 | 137 | // Get options for GPIO module, derived from the pin structure 138 | func (d *OdroidC1Driver) getGPIOOptions() map[string]interface{} { 139 | result := make(map[string]interface{}) 140 | 141 | pins := make(DTGPIOModulePinDefMap) 142 | 143 | // Add the GPIO pins to this map 144 | for i, pinConf := range d.pinConfigs { 145 | if pinConf.usedBy("gpio") { 146 | pins[Pin(i)] = &DTGPIOModulePinDef{pin: Pin(i), gpioLogical: pinConf.gpioLogical} 147 | } 148 | } 149 | result["pins"] = pins 150 | 151 | return result 152 | } 153 | 154 | // Get options for analog module, derived from the pin structure 155 | func (d *OdroidC1Driver) getAnalogOptions() map[string]interface{} { 156 | result := make(map[string]interface{}) 157 | 158 | pins := make(ODroidC1AnalogModulePinDefMap) 159 | 160 | // Add the GPIO pins to this map 161 | for i, pinConf := range d.pinConfigs { 162 | if pinConf.usedBy("analog") { 163 | pins[Pin(i)] = &ODroidC1AnalogModulePinDef{pin: Pin(i), analogLogical: pinConf.analogLogical} 164 | } 165 | } 166 | result["pins"] = pins 167 | 168 | return result 169 | } 170 | 171 | // Return the i2c options required to initialise that module. 172 | func (d *OdroidC1Driver) getI2COptions(module string) map[string]interface{} { 173 | result := make(map[string]interface{}) 174 | 175 | pins := make(DTI2CModulePins, 0) 176 | for i, pinConf := range d.pinConfigs { 177 | if pinConf.usedBy(module) { 178 | pins = append(pins, Pin(i)) 179 | } 180 | } 181 | 182 | result["pins"] = pins 183 | 184 | // TODO CALCULATE THIS FROM MODULE 185 | // this should really look at the device structure to ensure that I2C2 on hardware maps to /dev/i2c1. This confusion seems 186 | // to happen because of the way the kernel initialises the devices at boot time. 187 | if module == "i2ca" { 188 | result["device"] = "/dev/i2c-1" 189 | } else { 190 | result["device"] = "/dev/i2c-2" 191 | } 192 | 193 | return result 194 | } 195 | 196 | // internal function to get a Pin. It does not use GetPin because that relies on the driver having already been initialised. This 197 | // method can be called while still initialising. Only matches names[0], which is the Pn.nn expansion header name. 198 | func (d *OdroidC1Driver) getPin(name string) Pin { 199 | for i, hw := range d.pinConfigs { 200 | if hw.names[0] == name { 201 | return Pin(i) 202 | } 203 | } 204 | return Pin(0) 205 | } 206 | 207 | func (d *OdroidC1Driver) GetModules() map[string]Module { 208 | return d.modules 209 | } 210 | 211 | func (d *OdroidC1Driver) Close() { 212 | // Disable all the modules 213 | for _, module := range d.modules { 214 | module.Disable() 215 | } 216 | } 217 | 218 | func (d *OdroidC1Driver) PinMap() (pinMap HardwarePinMap) { 219 | pinMap = make(HardwarePinMap) 220 | 221 | for i, hw := range d.pinConfigs { 222 | pinMap.Add(Pin(i), hw.names, hw.modules) 223 | } 224 | 225 | return 226 | } 227 | -------------------------------------------------------------------------------- /driver_pi_dt.go: -------------------------------------------------------------------------------- 1 | package hwio 2 | 3 | // A driver for Raspberry Pi where device tree is supported (linux kernel 3.7+) 4 | // 5 | // Things known to work (tested on raspian 3.10+ kernel, rev 1 board): 6 | // - digital write on all support ed GPIO pins 7 | // - digital read on all GPIO pins, for modes INPUT. 8 | // 9 | // Known issues: 10 | // - INPUT_PULLUP and INPUT_PULLDOWN not implemented yet. 11 | // - no support yet for SPI, serial 12 | // 13 | // References: 14 | // - http://elinux.org/RPi_Low-level_peripherals 15 | // - https://projects.drogon.net/raspberry-pi/wiringpi/ 16 | // - BCM2835 technical reference 17 | 18 | import ( 19 | "os/exec" 20 | "strings" 21 | ) 22 | 23 | type pinoutRevision int 24 | const ( 25 | //type0ne is used for a Raspberry 1 26 | type0ne = iota 27 | //typeTwo is used for a Raspberry 2 28 | typeTwo 29 | //typeAplusBPlusZeroPi2 is used fo Raspberry Pi 1 Models A+ and B+, Pi 2 Model B, Pi 3 Model B and Pi Zero (and Zero W) 30 | typeAplusBPlusZeroPi2 31 | ) 32 | 33 | 34 | type RaspberryPiDTDriver struct { // all pins understood by the driver 35 | pinConfigs []*DTPinConfig 36 | 37 | // a map of module names to module objects, created at initialisation 38 | modules map[string]Module 39 | } 40 | 41 | func NewRaspPiDTDriver() *RaspberryPiDTDriver { 42 | return &RaspberryPiDTDriver{} 43 | } 44 | 45 | func (d *RaspberryPiDTDriver) MatchesHardwareConfig() bool { 46 | cpuinfo, e := exec.Command("cat", "/proc/cpuinfo").Output() 47 | if e != nil { 48 | return false 49 | } 50 | s := string(cpuinfo) 51 | if strings.Contains(s, "BCM2708") || strings.Contains(s, "BCM2709") || strings.Contains(s, "BCM2835") { 52 | return true 53 | } 54 | 55 | return false 56 | } 57 | 58 | func (d *RaspberryPiDTDriver) Init() error { 59 | d.createPinData() 60 | d.initialiseModules() 61 | 62 | return nil 63 | } 64 | 65 | // http://www.hobbytronics.co.uk/raspberry-pi-gpio-pinout 66 | func (d *RaspberryPiDTDriver) createPinData() { 67 | switch d.BoardRevision() { 68 | case type0ne: 69 | d.pinConfigs = []*DTPinConfig{ 70 | {[]string{"null"}, []string{"unassignable"}, 0, 0}, // 0 - spacer 71 | {[]string{"3.3v"}, []string{"unassignable"}, 0, 0}, 72 | {[]string{"5v"}, []string{"unassignable"}, 0, 0}, 73 | {[]string{"sda"}, []string{"i2c"}, 0, 0}, 74 | {[]string{"do-not-connect-1"}, []string{"unassignable"}, 0, 0}, 75 | {[]string{"scl"}, []string{"i2c"}, 0, 0}, 76 | {[]string{"ground"}, []string{"unassignable"}, 0, 0}, 77 | {[]string{"gpio4"}, []string{"gpio"}, 4, 0}, 78 | {[]string{"txd"}, []string{"serial"}, 0, 0}, 79 | {[]string{"do-not-connect-2"}, []string{"unassignable"}, 0, 0}, 80 | {[]string{"rxd"}, []string{"serial"}, 0, 0}, 81 | {[]string{"gpio17"}, []string{"gpio"}, 17, 0}, 82 | {[]string{"gpio18"}, []string{"gpio"}, 18, 0}, // also supports PWM 83 | {[]string{"gpio21"}, []string{"gpio"}, 21, 0}, 84 | {[]string{"do-not-connect-3"}, []string{"unassignable"}, 0, 0}, 85 | {[]string{"gpio22"}, []string{"gpio"}, 22, 0}, 86 | {[]string{"gpio23"}, []string{"gpio"}, 23, 0}, 87 | {[]string{"do-not-connect-4"}, []string{"unassignable"}, 0, 0}, 88 | {[]string{"gpio24"}, []string{"gpio"}, 24, 0}, 89 | {[]string{"mosi"}, []string{"spi"}, 0, 0}, 90 | {[]string{"do-not-connect-5"}, []string{"unassignable"}, 0, 0}, 91 | {[]string{"miso"}, []string{"spi"}, 0, 0}, 92 | {[]string{"gpio25"}, []string{"gpio"}, 25, 0}, 93 | {[]string{"sclk"}, []string{"spi"}, 0, 0}, 94 | {[]string{"ce0n"}, []string{"spi"}, 0, 0}, 95 | {[]string{"do-not-connect-6"}, []string{"unassignable"}, 0, 0}, 96 | {[]string{"ce1n"}, []string{"spi"}, 0, 0}, 97 | } 98 | case typeTwo: 99 | d.pinConfigs = []*DTPinConfig{ 100 | {[]string{"null"}, []string{"unassignable"}, 0, 0}, // 0 - spacer 101 | {[]string{"3.3v-1"}, []string{"unassignable"}, 0, 0}, 102 | {[]string{"5v-1"}, []string{"unassignable"}, 0, 0}, 103 | {[]string{"sda"}, []string{"i2c"}, 0, 0}, 104 | {[]string{"5v-2"}, []string{"unassignable"}, 0, 0}, 105 | {[]string{"scl"}, []string{"i2c"}, 0, 0}, 106 | {[]string{"ground-1"}, []string{"unassignable"}, 0, 0}, 107 | {[]string{"gpio4"}, []string{"gpio"}, 4, 0}, 108 | {[]string{"txd"}, []string{"serial"}, 0, 0}, 109 | {[]string{"ground-2"}, []string{"unassignable"}, 0, 0}, 110 | {[]string{"rxd"}, []string{"serial"}, 0, 0}, 111 | {[]string{"gpio17"}, []string{"gpio"}, 17, 0}, 112 | {[]string{"gpio18"}, []string{"gpio"}, 18, 0}, // also supports PWM 113 | {[]string{"gpio27"}, []string{"gpio"}, 27, 0}, 114 | {[]string{"ground-3"}, []string{"unassignable"}, 0, 0}, 115 | {[]string{"gpio22"}, []string{"gpio"}, 22, 0}, 116 | {[]string{"gpio23"}, []string{"gpio"}, 23, 0}, 117 | {[]string{"3.3v-2"}, []string{"unassignable"}, 0, 0}, 118 | {[]string{"gpio24"}, []string{"gpio"}, 24, 0}, 119 | {[]string{"mosi"}, []string{"spi"}, 0, 0}, 120 | {[]string{"ground-4"}, []string{"unassignable"}, 0, 0}, 121 | {[]string{"miso"}, []string{"spi"}, 0, 0}, 122 | {[]string{"gpio25"}, []string{"gpio"}, 25, 0}, 123 | {[]string{"sclk"}, []string{"spi"}, 0, 0}, 124 | {[]string{"gpio8"}, []string{"gpio"}, 8, 0}, 125 | {[]string{"ground-5"}, []string{"unassignable"}, 0, 0}, 126 | {[]string{"gpio7"}, []string{"gpio"}, 7, 0}, 127 | } 128 | case typeAplusBPlusZeroPi2: // B+ 129 | d.pinConfigs = []*DTPinConfig{ 130 | {[]string{"null"}, []string{"unassignable"}, 0, 0}, // 0 - spacer 131 | {[]string{"3.3v-1"}, []string{"unassignable"}, 0, 0}, 132 | {[]string{"5v-1"}, []string{"unassignable"}, 0, 0}, 133 | {[]string{"sda"}, []string{"i2c"}, 0, 0}, 134 | {[]string{"5v-2"}, []string{"unassignable"}, 0, 0}, 135 | {[]string{"scl"}, []string{"i2c"}, 0, 0}, 136 | {[]string{"ground-1"}, []string{"unassignable"}, 0, 0}, 137 | {[]string{"gpio4"}, []string{"gpio"}, 4, 0}, 138 | {[]string{"txd"}, []string{"serial"}, 0, 0}, 139 | {[]string{"ground-2"}, []string{"unassignable"}, 0, 0}, 140 | {[]string{"rxd"}, []string{"serial"}, 0, 0}, 141 | {[]string{"gpio17"}, []string{"gpio"}, 17, 0}, 142 | {[]string{"gpio18"}, []string{"gpio"}, 18, 0}, // also supports PWM 143 | {[]string{"gpio27"}, []string{"gpio"}, 21, 0}, 144 | {[]string{"ground-3"}, []string{"unassignable"}, 0, 0}, 145 | {[]string{"gpio22"}, []string{"gpio"}, 22, 0}, 146 | {[]string{"gpio23"}, []string{"gpio"}, 23, 0}, 147 | {[]string{"3.3v-2"}, []string{"unassignable"}, 0, 0}, 148 | {[]string{"gpio24"}, []string{"gpio"}, 24, 0}, 149 | {[]string{"mosi"}, []string{"spi"}, 0, 0}, 150 | {[]string{"ground-4"}, []string{"unassignable"}, 0, 0}, 151 | {[]string{"miso"}, []string{"spi"}, 0, 0}, 152 | {[]string{"gpio25"}, []string{"gpio"}, 25, 0}, 153 | {[]string{"sclk"}, []string{"spi"}, 0, 0}, 154 | {[]string{"gpio8"}, []string{"gpio"}, 8, 0}, 155 | {[]string{"ground-5"}, []string{"unassignable"}, 0, 0}, 156 | {[]string{"gpio7"}, []string{"gpio"}, 7, 0}, 157 | {[]string{"do-not-connect-1"}, []string{"unassignable"}, 0, 0}, 158 | {[]string{"do-not-connect-2"}, []string{"unassignable"}, 0, 0}, 159 | {[]string{"gpio5"}, []string{"gpio"}, 5, 0}, 160 | {[]string{"ground-6"}, []string{"unassignable"}, 0, 0}, 161 | {[]string{"gpio6"}, []string{"gpio"}, 6, 0}, 162 | {[]string{"gpio12"}, []string{"gpio"}, 12, 0}, 163 | {[]string{"gpio13"}, []string{"gpio"}, 13, 0}, 164 | {[]string{"ground-7"}, []string{"unassignable"}, 0, 0}, 165 | {[]string{"gpio19"}, []string{"gpio"}, 19, 0}, 166 | {[]string{"gpio16"}, []string{"gpio"}, 16, 0}, 167 | {[]string{"gpio26"}, []string{"gpio"}, 26, 0}, 168 | {[]string{"gpio20"}, []string{"gpio"}, 20, 0}, 169 | {[]string{"ground-8"}, []string{"unassignable"}, 0, 0}, 170 | {[]string{"gpio21"}, []string{"gpio"}, 21, 0}, 171 | } 172 | } 173 | } 174 | 175 | func (d *RaspberryPiDTDriver) initialiseModules() error { 176 | d.modules = make(map[string]Module) 177 | 178 | gpio := NewDTGPIOModule("gpio") 179 | e := gpio.SetOptions(d.getGPIOOptions()) 180 | if e != nil { 181 | return e 182 | } 183 | 184 | i2c := NewDTI2CModule("i2c") 185 | e = i2c.SetOptions(d.getI2COptions()) 186 | if e != nil { 187 | return e 188 | } 189 | 190 | // Create the leds module which is BBB-specific. There are no options. 191 | leds := NewDTLEDModule("leds") 192 | e = leds.SetOptions(d.getLEDOptions("leds")) 193 | if e != nil { 194 | return e 195 | } 196 | 197 | d.modules["gpio"] = gpio 198 | d.modules["i2c"] = i2c 199 | d.modules["leds"] = leds 200 | 201 | return nil 202 | } 203 | 204 | // Get options for GPIO module, derived from the pin structure 205 | func (d *RaspberryPiDTDriver) getGPIOOptions() map[string]interface{} { 206 | result := make(map[string]interface{}) 207 | 208 | pins := make(DTGPIOModulePinDefMap) 209 | 210 | // Add the GPIO pins to this map 211 | for i, hw := range d.pinConfigs { 212 | if hw.modules[0] == "gpio" { 213 | pins[Pin(i)] = &DTGPIOModulePinDef{pin: Pin(i), gpioLogical: hw.gpioLogical} 214 | } 215 | } 216 | result["pins"] = pins 217 | 218 | return result 219 | } 220 | 221 | func (d *RaspberryPiDTDriver) getI2COptions() map[string]interface{} { 222 | result := make(map[string]interface{}) 223 | 224 | pins := make(DTI2CModulePins, 0) 225 | pins = append(pins, Pin(3)) 226 | pins = append(pins, Pin(5)) 227 | 228 | result["pins"] = pins 229 | 230 | if d.BoardRevision() == type0ne { 231 | result["device"] = "/dev/i2c-0" 232 | } else { 233 | result["device"] = "/dev/i2c-1" 234 | } 235 | 236 | return result 237 | } 238 | 239 | func (d *RaspberryPiDTDriver) getLEDOptions(name string) map[string]interface{} { 240 | result := make(map[string]interface{}) 241 | 242 | pins := make(DTLEDModulePins) 243 | pins["ok"] = "/sys/class/leds/led0/" 244 | 245 | result["pins"] = pins 246 | 247 | return result 248 | } 249 | 250 | // Determine the version of Raspberry Pi. 251 | // This discussion http://www.raspberrypi.org/phpBB3/viewtopic.php?f=44&t=23989 252 | // was used to determine the algorithm, specifically the comment by gordon@drogon.net 253 | // It will return 1 or 2. 254 | func (d *RaspberryPiDTDriver) BoardRevision() pinoutRevision { 255 | revision := CpuInfo(0, "Revision") 256 | switch revision { 257 | case "0002", "0003": 258 | return type0ne 259 | case "0010": 260 | return typeAplusBPlusZeroPi2 261 | } 262 | 263 | // Pi 2 boards have different strings, but pinout is the same as B+ 264 | revision = CpuInfo(0, "CPU revision") 265 | switch revision { 266 | case "5": 267 | return typeAplusBPlusZeroPi2 268 | case "7": //PI zero + 269 | return typeAplusBPlusZeroPi2 270 | } 271 | 272 | return typeTwo 273 | } 274 | 275 | func (d *RaspberryPiDTDriver) GetModules() map[string]Module { 276 | return d.modules 277 | } 278 | 279 | func (d *RaspberryPiDTDriver) Close() { 280 | // Disable all the modules 281 | for _, module := range d.modules { 282 | module.Disable() 283 | } 284 | } 285 | 286 | func (d *RaspberryPiDTDriver) PinMap() (pinMap HardwarePinMap) { 287 | pinMap = make(HardwarePinMap) 288 | 289 | for i, hw := range d.pinConfigs { 290 | pinMap.Add(Pin(i), hw.names, hw.modules) 291 | } 292 | 293 | return 294 | } 295 | -------------------------------------------------------------------------------- /dt_pin_config.go: -------------------------------------------------------------------------------- 1 | package hwio 2 | 3 | // DTPinConfig represents configuration information for a pin used in a device-tree based driver, 4 | // which typically hold the same details. 5 | type DTPinConfig struct { 6 | // A list of names that this pin will be known by. They are effectively synonyms; retrieving a pin 7 | // by one of these names does not automatically cause the pin's behaviour to change. 8 | names []string 9 | 10 | // Names of modules that may allocate this pin 11 | modules []string 12 | 13 | // logical number for GPIO, for pins used by "gpio" module. This is the GPIO port number plus the GPIO pin within the port. 14 | gpioLogical int 15 | 16 | // analog pin number, for pins used by "analog" module 17 | analogLogical int 18 | } 19 | 20 | // Determine if the pin is used by the module 21 | func (c *DTPinConfig) usedBy(module string) bool { 22 | for _, n := range c.modules { 23 | if n == module { 24 | return true 25 | } 26 | } 27 | return false 28 | } 29 | -------------------------------------------------------------------------------- /examples/blink.go: -------------------------------------------------------------------------------- 1 | // Blink 2 | // 3 | // Blinks an LED for one second on, one second off. 4 | // This is heavily annotated, with error handling. 5 | 6 | package main 7 | 8 | import ( 9 | "fmt" 10 | "os" 11 | 12 | "github.com/mrmorphic/hwio" 13 | ) 14 | 15 | func main() { 16 | // get a pin by name. You could also just use the logical pin number, but this is 17 | // more readable. On BeagleBone, USR0 is an on-board LED. 18 | ledPin, err := hwio.GetPin("USR1") 19 | 20 | // Generally we wouldn't expect an error, but perhaps someone is running this a 21 | if err != nil { 22 | fmt.Println(err) 23 | os.Exit(1) 24 | } 25 | 26 | // Set the mode of the pin to output. This will return an error if, for example, 27 | // we were trying to set an analog input to an output. 28 | err = hwio.PinMode(ledPin, hwio.OUTPUT) 29 | 30 | if err != nil { 31 | fmt.Println(err) 32 | os.Exit(1) 33 | } 34 | 35 | // Run the blink forever 36 | for { 37 | hwio.DigitalWrite(ledPin, hwio.HIGH) 38 | hwio.Delay(1000) 39 | hwio.DigitalWrite(ledPin, hwio.LOW) 40 | hwio.Delay(1000) 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /examples/pinmap.go: -------------------------------------------------------------------------------- 1 | // pinmap 2 | // 3 | // This just prints a map of pins for the device you're using. The maps shows the 4 | // logical pin number, the name or names that the driver knows the pin by, 5 | // and the set of capabilities that the driver supports for that pin. 6 | 7 | package main 8 | 9 | import ( 10 | "github.com/mrmorphic/hwio" 11 | ) 12 | 13 | func main() { 14 | hwio.DebugPinMap() 15 | } 16 | -------------------------------------------------------------------------------- /examples/shiftout.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | // An example of shifting 8 bit data to a 74HC595 shift register. 4 | // Implements a continuous 8-bit binary counter. 5 | 6 | import ( 7 | "github.com/mrmorphic/hwio" 8 | ) 9 | 10 | func main() { 11 | // Get the pins we're going to use. These are on a beaglebone. 12 | dataPin, _ := hwio.GetPin("P8.3") // connected to pin 14 13 | clockPin, _ := hwio.GetPin("P8.4") // connected to pin 11 14 | storePin, _ := hwio.GetPin("P8.5") // connected to pin 12 15 | 16 | // Make them all outputs 17 | hwio.PinMode(dataPin, hwio.OUTPUT) 18 | hwio.PinMode(clockPin, hwio.OUTPUT) 19 | hwio.PinMode(storePin, hwio.OUTPUT) 20 | 21 | data := 0 22 | 23 | // Set the initial state of the clock and store pins to low. These both 24 | // trigger on the rising edge. 25 | hwio.DigitalWrite(clockPin, hwio.LOW) 26 | hwio.DigitalWrite(storePin, hwio.LOW) 27 | 28 | for { 29 | // Shift out 8 bits 30 | hwio.ShiftOut(dataPin, clockPin, uint(data), hwio.MSBFIRST) 31 | 32 | // You can use this line instead to clock out 16 bits if you have 33 | // two 74HC595's chained together 34 | // hwio.ShiftOutSize(dataPin, clockPin, uint(data), hwio.MSBFIRST, 16) 35 | 36 | // Pulse the store pin once 37 | hwio.DigitalWrite(storePin, hwio.HIGH) 38 | hwio.DigitalWrite(storePin, hwio.LOW) 39 | 40 | // Poor humans cannot read binary as fast as the machine can display it 41 | hwio.Delay(100) 42 | 43 | data++ 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /examples/tlc5940.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | // An example of shifting 8 bit data to a TLC5940 shift register. 4 | // Implements a continuous 8-bit binary counter. 5 | 6 | import ( 7 | "fmt" 8 | 9 | "github.com/mrmorphic/hwio" 10 | ) 11 | 12 | func main() { 13 | // Get the pins we're going to use. These are on a beaglebone. 14 | sinPin, _ := hwio.GetPin("P9.11") 15 | sclkPin, _ := hwio.GetPin("P9.12") 16 | xlatPin, _ := hwio.GetPin("P9.13") 17 | gsclkPin, _ := hwio.GetPin("P9.14") 18 | blankPin, _ := hwio.GetPin("P9.15") 19 | 20 | fmt.Printf("Pins are: sin=%d, sclk=%d, xlat=%d, gsclk=%d, blank=%d", sinPin, sclkPin, xlatPin, gsclkPin, blankPin) 21 | // Make them all outputs 22 | e := hwio.PinMode(sinPin, hwio.OUTPUT) 23 | if e == nil { 24 | hwio.PinMode(sclkPin, hwio.OUTPUT) 25 | } 26 | if e == nil { 27 | hwio.PinMode(xlatPin, hwio.OUTPUT) 28 | } 29 | if e == nil { 30 | hwio.PinMode(gsclkPin, hwio.OUTPUT) 31 | } 32 | if e == nil { 33 | hwio.PinMode(blankPin, hwio.OUTPUT) 34 | } 35 | if e != nil { 36 | fmt.Printf("Could not initialise pins: %s", e) 37 | return 38 | } 39 | 40 | // set clocks low 41 | hwio.DigitalWrite(sclkPin, hwio.LOW) 42 | hwio.DigitalWrite(xlatPin, hwio.LOW) 43 | hwio.DigitalWrite(gsclkPin, hwio.LOW) 44 | 45 | // run GS clock in it's own space 46 | hwio.DigitalWrite(blankPin, hwio.HIGH) 47 | hwio.DigitalWrite(blankPin, hwio.LOW) 48 | clockData(gsclkPin) 49 | 50 | for b := 0; b < 4096; b++ { 51 | writeData(uint(b), sinPin, sclkPin, xlatPin) 52 | 53 | for j := 0; j < 10; j++ { 54 | hwio.DigitalWrite(blankPin, hwio.HIGH) 55 | hwio.DigitalWrite(blankPin, hwio.LOW) 56 | clockData(gsclkPin) 57 | } 58 | 59 | // hwio.Delay(100) 60 | } 61 | 62 | // hwio.ShiftOut(dataPin, clockPin, uint(data), hwio.MSBFIRST) 63 | } 64 | 65 | // val is a 12-bit int 66 | func writeData(val uint, sinPin hwio.Pin, sclkPin hwio.Pin, xlatPin hwio.Pin) { 67 | fmt.Printf("writing data %d\n", val) 68 | bits := 0 69 | mask := uint(1) << 11 70 | for i := 0; i < 16; i++ { 71 | v := val 72 | for j := 0; j < 12; j++ { 73 | if (v & mask) != 0 { 74 | hwio.DigitalWrite(sinPin, hwio.HIGH) 75 | } else { 76 | hwio.DigitalWrite(sinPin, hwio.HIGH) 77 | } 78 | hwio.DigitalWrite(sclkPin, hwio.HIGH) 79 | hwio.DigitalWrite(sclkPin, hwio.LOW) 80 | 81 | v = v << 1 82 | bits++ 83 | } 84 | } 85 | 86 | hwio.DigitalWrite(xlatPin, hwio.HIGH) 87 | hwio.DigitalWrite(xlatPin, hwio.LOW) 88 | fmt.Printf("Wrote %d bits\n", bits) 89 | } 90 | 91 | func clockData(gsclkPin hwio.Pin) { 92 | for g := 0; g < 4096; g++ { 93 | hwio.DigitalWrite(gsclkPin, hwio.HIGH) 94 | hwio.DigitalWrite(gsclkPin, hwio.LOW) 95 | } 96 | } 97 | -------------------------------------------------------------------------------- /hwio.go: -------------------------------------------------------------------------------- 1 | /* 2 | Package hwio implements a simple Arduino-like interface for controlling 3 | hardware I/O, with configurable backends depending on the device. 4 | */ 5 | package hwio 6 | 7 | import ( 8 | "errors" 9 | "fmt" 10 | "os" 11 | "path/filepath" 12 | "strings" 13 | "time" 14 | ) 15 | 16 | type BitShiftOrder byte 17 | 18 | const ( 19 | LSBFIRST BitShiftOrder = iota 20 | MSBFIRST 21 | ) 22 | 23 | // Reference to driver we're using 24 | var driver HardwareDriver 25 | 26 | // Retrieved from the driver, this is the map of the hardware pins supported by 27 | // the driver and their capabilities 28 | var definedPins HardwarePinMap 29 | 30 | // A private type for associating a pin's definition with the current IO mode 31 | // and any other dynamic properties of the pin. 32 | type assignedPin struct { 33 | pin Pin // pin being assigned 34 | module Module // module that has assigned this pin 35 | // pinIOMode PinIOMode // mode that was assigned to this pin 36 | } 37 | 38 | // A map of pin numbers to the assigned dynamic properties of the pin. This is 39 | // set by PinMode when errorChecking is on, and can be used by other functions 40 | // to determine if the request is valid given the assigned properties of the pin. 41 | var assignedPins map[Pin]*assignedPin 42 | 43 | // If set to true, functions should test that their constraints are met. 44 | // e.g. test that the pin is capable of doing what is asked. This can be set 45 | // with SetErrorChecking(). Setting to false bypasses checks for performance. 46 | // By default turned on, which is a better default for beginners. 47 | var errorChecking bool = true 48 | 49 | // init() attempts to determine from the environment what the driver is. The 50 | // intent is that the consumer of the library would not generally have to worry 51 | // about it, it would just work. If it cannot determine the driver, it doesn't 52 | // set the driver to anything. 53 | func init() { 54 | assignedPins = make(map[Pin]*assignedPin) 55 | determineDriver() 56 | } 57 | 58 | func fileExists(name string) bool { 59 | _, err := os.Stat(name) 60 | if err != nil { 61 | return false 62 | } 63 | 64 | return true 65 | } 66 | 67 | // Work out the driver from environment if we can. If we have any problems, 68 | // don't generate an error, just return with the driver not set. 69 | func determineDriver() { 70 | drivers := [...]HardwareDriver{NewBeagleboneBlackDTDriver(), NewRaspPiDTDriver(), NewOdroidC1Driver()} 71 | for _, d := range drivers { 72 | if d.MatchesHardwareConfig() { 73 | SetDriver(d) 74 | return 75 | } 76 | } 77 | 78 | fmt.Printf("Unable to select a suitable driver for this board.\n") 79 | } 80 | 81 | // Check if the driver is assigned. If not, return an error to indicate that, 82 | // otherwise return no error. 83 | func assertDriver() error { 84 | if driver == nil { 85 | return errors.New("hwio has no configured driver") 86 | } 87 | return nil 88 | } 89 | 90 | // Set the driver. Also calls Init on the driver, and loads the capabilities 91 | // of the device. 92 | func SetDriver(d HardwareDriver) { 93 | driver = d 94 | e := driver.Init() 95 | if e != nil { 96 | fmt.Printf("Could not initialise driver: %s", e) 97 | } 98 | definedPins = driver.PinMap() 99 | } 100 | 101 | // Retrieve the current hardware driver. 102 | func GetDriver() HardwareDriver { 103 | return driver 104 | } 105 | 106 | // Returns a map of the hardware pins. This will only work once the driver is 107 | // set. 108 | func GetDefinedPins() HardwarePinMap { 109 | return definedPins 110 | } 111 | 112 | // Ensure that any resources external to the program that have been allocated are tidied up. 113 | func CloseAll() { 114 | if driver == nil { 115 | return 116 | } 117 | driver.Close() 118 | } 119 | 120 | // Returns a Pin given a canonical name for the pin. 121 | // e.g. to get the pin number of P8.13 on a beaglebone, 122 | // pin := hwio.GetPin("P8.13") 123 | // Order of search is: 124 | // - search hwRefs in the pin map in order. 125 | // This function should not generally be relied on for performance. For max speed, call this 126 | // for each pin you use once on init, and use the returned Pin values thereafter. 127 | // Search is case sensitive at the moment 128 | // @todo GetPin: consider making it case-insensitive on name 129 | // @todo GetPin: consider allowing an int or int as string to identify logical pin directly 130 | func GetPin(pinName string) (Pin, error) { 131 | pl := strings.ToLower(pinName) 132 | for pin, pinDef := range definedPins { 133 | for _, name := range pinDef.names { 134 | if strings.ToLower(name) == pl { 135 | return pin, nil 136 | } 137 | } 138 | } 139 | 140 | return Pin(0), fmt.Errorf("Could not find a pin called %s", pinName) 141 | } 142 | 143 | // Shortcut for calling GetPin and then PinMode. 144 | func GetPinWithMode(cname string, mode PinIOMode) (pin Pin, e error) { 145 | p, e := GetPin(cname) 146 | if e != nil { 147 | return 148 | } 149 | 150 | e = PinMode(p, mode) 151 | return p, e 152 | } 153 | 154 | // Set error checking. This should be called before pin assignments. 155 | func SetErrorChecking(check bool) { 156 | errorChecking = check 157 | } 158 | 159 | // Helper function to get GPIO module 160 | func GetGPIOModule() (GPIOModule, error) { 161 | m, e := GetModule("gpio") 162 | if e != nil { 163 | return nil, e 164 | } 165 | 166 | if m == nil { 167 | return nil, errors.New("Driver does not support GPIO") 168 | } 169 | 170 | return m.(GPIOModule), nil 171 | } 172 | 173 | // Given an internal pin number, return the canonical name for the pin, as defined by the driver. If the pin 174 | // is not to the driver, return "". 175 | func PinName(pin Pin) string { 176 | p := definedPins[pin] 177 | if p == nil { 178 | return "" 179 | } 180 | return p.names[0] 181 | } 182 | 183 | // Set the mode of a pin. Analogous to Arduino pin mode. 184 | func PinMode(pin Pin, mode PinIOMode) error { 185 | gpio, e := GetGPIOModule() 186 | if e != nil { 187 | return e 188 | } 189 | 190 | return gpio.PinMode(pin, mode) 191 | } 192 | 193 | // Close a specific pin that has been assigned as GPIO by PinMode 194 | func ClosePin(pin Pin) error { 195 | gpio, e := GetGPIOModule() 196 | if e != nil { 197 | return e 198 | } 199 | 200 | return gpio.ClosePin(pin) 201 | } 202 | 203 | // Assign a pin to a module. This is typically called by modules when they allocate pins. If the pin is already assigned, 204 | // an error is generated. ethod is public in case it is needed to hack around default driver settings. 205 | func AssignPin(pin Pin, module Module) error { 206 | if a := assignedPins[pin]; a != nil { 207 | return fmt.Errorf("Pin %d is already assigned to module %s", pin, a.module.GetName()) 208 | } 209 | assignedPins[pin] = &assignedPin{pin, module} 210 | return nil 211 | } 212 | 213 | // Assign a set of pins. Method is public in case it is needed to hack around default driver settings. 214 | func AssignPins(pins PinList, module Module) error { 215 | for _, pin := range pins { 216 | e := AssignPin(pin, module) 217 | if e != nil { 218 | return e 219 | } 220 | } 221 | return nil 222 | } 223 | 224 | // Unassign a pin. Method is public in case it is needed to hack around default driver settings. 225 | func UnassignPin(pin Pin) error { 226 | delete(assignedPins, pin) 227 | return nil 228 | } 229 | 230 | // Unassign a set of pins. Method is public in case it is needed to hack around default driver settings. 231 | func UnassignPins(pins PinList) (er error) { 232 | er = nil 233 | 234 | for _, pin := range pins { 235 | e := UnassignPin(pin) 236 | if e != nil { 237 | er = e 238 | } 239 | } 240 | 241 | return 242 | } 243 | 244 | // Write a value to a digital pin 245 | func DigitalWrite(pin Pin, value int) (e error) { 246 | gpio, e := GetGPIOModule() 247 | if e != nil { 248 | return e 249 | } 250 | 251 | return gpio.DigitalWrite(pin, value) 252 | } 253 | 254 | // Read a value from a digital pin 255 | func DigitalRead(pin Pin) (result int, e error) { 256 | // @todo consider memoizing 257 | gpio, e := GetGPIOModule() 258 | if e != nil { 259 | return 0, e 260 | } 261 | 262 | return gpio.DigitalRead(pin) 263 | } 264 | 265 | // given a logic level of HIGH or LOW, return the opposite. Invalid values returned as LOW. 266 | func Negate(logicLevel int) int { 267 | if logicLevel == LOW { 268 | return HIGH 269 | } 270 | return LOW 271 | } 272 | 273 | // Helper function to pulse a pin, which must have been set as GPIO. 274 | // 'active' is LOW or HIGH. Pulse sets pin to inactive, then active for 275 | // 'durationMicroseconds' and the back to inactive. 276 | func Pulse(pin Pin, active int, durationMicroseconds int) error { 277 | // set to inactive state, in case it wasn't already 278 | e := DigitalWrite(pin, Negate(active)) 279 | if e != nil { 280 | return e 281 | } 282 | 283 | // set to active state 284 | e = DigitalWrite(pin, active) 285 | if e != nil { 286 | return e 287 | } 288 | 289 | DelayMicroseconds(durationMicroseconds) 290 | 291 | // finally reset to inactive state 292 | return DigitalWrite(pin, Negate(active)) 293 | } 294 | 295 | // Helper function to get GPIO module 296 | func GetAnalogModule() (AnalogModule, error) { 297 | m, e := GetModule("analog") 298 | if e != nil { 299 | return nil, e 300 | } 301 | 302 | if m == nil { 303 | return nil, errors.New("Driver does not support analog") 304 | } 305 | 306 | return m.(AnalogModule), nil 307 | } 308 | 309 | // Read an analog value from a pin. The range of values is hardware driver dependent. 310 | func AnalogRead(pin Pin) (int, error) { 311 | analog, e := GetAnalogModule() 312 | if e != nil { 313 | return 0, e 314 | } 315 | 316 | return analog.AnalogRead(pin) 317 | } 318 | 319 | // Helper to turn an on-board LED on or off. Uses LED module 320 | func Led(name string, on bool) error { 321 | m, e := GetModule("leds") 322 | if e != nil { 323 | return e 324 | } 325 | 326 | leds := m.(LEDModule) 327 | led, e := leds.GetLED(name) 328 | if e != nil { 329 | return e 330 | } 331 | 332 | e = led.SetTrigger("none") 333 | if e != nil { 334 | return e 335 | } 336 | 337 | return led.SetOn(on) 338 | } 339 | 340 | // Delay execution by the specified number of milliseconds. This is a helper 341 | // function for similarity with Arduino. It is implemented using standard go 342 | // time package. 343 | func Delay(duration int) { 344 | time.Sleep(time.Duration(duration) * time.Millisecond) 345 | } 346 | 347 | // Delay execution by the specified number of microseconds. This is a helper 348 | // function for similarity with Arduino. It is implemented using standard go 349 | // time package 350 | func DelayMicroseconds(duration int) { 351 | time.Sleep(time.Duration(duration) * time.Microsecond) 352 | } 353 | 354 | // @todo DebugPinMap: sort 355 | func DebugPinMap() { 356 | fmt.Println("HardwarePinMap:") 357 | for key, val := range definedPins { 358 | fmt.Printf("Pin %d: %s\n", key, val.String()) 359 | } 360 | fmt.Printf("\n") 361 | } 362 | 363 | // The approximate mapping of Arduino shiftOut, this shifts a byte out on the 364 | // data pin, pulsing the clock pin high and then low. 365 | func ShiftOut(dataPin Pin, clockPin Pin, value uint, order BitShiftOrder) error { 366 | return ShiftOutSize(dataPin, clockPin, value, order, 8) 367 | } 368 | 369 | // More generic version of ShiftOut which shifts out n of data from value. The 370 | // value shifted out is always the lowest n bits of the value, but 'order' 371 | // determines whether the msb or lsb from that value are shifted first 372 | func ShiftOutSize(dataPin Pin, clockPin Pin, value uint, order BitShiftOrder, n uint) error { 373 | bit := uint(0) 374 | v := value 375 | mask := uint(1) << (n - 1) 376 | for i := uint(0); i < n; i++ { 377 | // get the next bit 378 | if order == LSBFIRST { 379 | bit = v & 1 380 | v = v >> 1 381 | } else { 382 | bit = v & mask 383 | if bit != 0 { 384 | bit = 1 385 | } 386 | v = v << 1 387 | } 388 | // write to data pin 389 | e := DigitalWrite(dataPin, int(bit)) 390 | if e != nil { 391 | return e 392 | } 393 | // pulse clock high and then low 394 | e = DigitalWrite(clockPin, HIGH) 395 | if e != nil { 396 | return e 397 | } 398 | DigitalWrite(clockPin, LOW) 399 | } 400 | return nil 401 | } 402 | 403 | // Given an integer and a list of GPIO pins (that must have been set up as outputs), write the integer across 404 | // the pins. The number of bits is determined by the length of the pins. The most-significant output pin is first. 405 | // Bits are written MSB first. 406 | // Maximum number of bits that can be shifted is 32. 407 | // Note that the bits are not written out instantaneously, although very quickly. If you need instantaneous changing of 408 | // all pins, you need to consider an output buffer. 409 | func WriteUIntToPins(value uint32, pins []Pin) error { 410 | if len(pins) > 31 { 411 | return errors.New("WriteUIntToPins only supports up to 32 bits") 412 | } 413 | 414 | bit := uint32(0) 415 | v := value 416 | mask := uint32(1) << uint32((len(pins) - 1)) 417 | for i := uint32(0); i < uint32(len(pins)); i++ { 418 | bit = v & mask 419 | if bit != 0 { 420 | bit = 1 421 | } 422 | v = v << 1 423 | // write to data pin 424 | // fmt.Printf("Writing %s to pin %s\n", bit, pins[i]) 425 | e := DigitalWrite(pins[i], int(bit)) 426 | if e != nil { 427 | return e 428 | } 429 | } 430 | return nil 431 | } 432 | 433 | // Write a string to a file and close it again. 434 | func WriteStringToFile(filename string, value string) error { 435 | // fmt.Printf("writing %s to file %s\n", value, filename) 436 | f, e := os.OpenFile(filename, os.O_WRONLY|os.O_TRUNC, 0666) 437 | if e != nil { 438 | return e 439 | } 440 | defer f.Close() 441 | 442 | _, e = f.WriteString(value) 443 | return e 444 | } 445 | 446 | // Given a glob pattern, return the full path of the first matching file 447 | func findFirstMatchingFile(glob string) (string, error) { 448 | matches, e := filepath.Glob(glob) 449 | if e != nil { 450 | return "", e 451 | } 452 | 453 | if len(matches) >= 1 { 454 | return matches[0], nil 455 | } 456 | return "", nil 457 | } 458 | 459 | func Map(value int, fromLow int, fromHigh int, toLow int, toHigh int) int { 460 | return (value-fromLow)*(toHigh-toLow)/(fromHigh-fromLow) + toLow 461 | } 462 | 463 | // Given a high and low byte, combine to form a single 16-bit value 464 | func UInt16FromUInt8(highByte byte, lowByte byte) uint16 { 465 | return uint16(uint16(highByte)<<8) | uint16(lowByte) 466 | } 467 | 468 | func ReverseBytes16(value uint16) uint16 { 469 | // @todo implement ReverseBytes16() 470 | return 0 471 | } 472 | 473 | func ReverseBytes32(value uint32) uint32 { 474 | // @todo implement ReverseBytes32() 475 | return 0 476 | } 477 | 478 | // Get a module by name. If driver is not set, it will return an error. If the driver does not support that module, 479 | // 480 | func GetModule(name string) (Module, error) { 481 | driver := GetDriver() 482 | if driver == nil { 483 | return nil, errors.New("GetModule: Driver is not set") 484 | } 485 | 486 | modules := driver.GetModules() 487 | return modules[name], nil 488 | } 489 | 490 | // This is the interface that hardware drivers implement. Generally all drivers are created 491 | // but not initialised. If MatchesHardwareConfig() is true and the driver is selected, Init() 492 | // will be called. 493 | type HardwareDriver interface { 494 | // Each driver is responsible for evaluating whether it applies to the current hardware 495 | // configuration or not. If this function returns false, the driver will not be used and Init 496 | // will not be called. If this function returns true, the driver may be called, in which case 497 | // Init will then be called 498 | MatchesHardwareConfig() bool 499 | 500 | // Initialise the driver. 501 | Init() (e error) 502 | 503 | // Return a module by name, or nil if undefined. The module names can be different between types of boards. 504 | GetModules() map[string]Module 505 | 506 | // Return the pin map for the driver, listing all supported pins and their capabilities 507 | PinMap() (pinMap HardwarePinMap) 508 | 509 | // Close the driver before destruction 510 | Close() 511 | } 512 | -------------------------------------------------------------------------------- /hwio_test.go: -------------------------------------------------------------------------------- 1 | package hwio 2 | 3 | // Unit tests for hwio. Each test sets a new TestDriver instance to start with 4 | // same uninitialised state. 5 | 6 | import ( 7 | "fmt" 8 | "testing" 9 | ) 10 | 11 | // Get the driver's pin map and check for the pins in it. Tests that the 12 | // consumer can determine pin capabilities 13 | func TestPinMap(t *testing.T) { 14 | SetDriver(new(TestDriver)) 15 | 16 | m := GetDefinedPins() 17 | 18 | // P0 should exist 19 | p0 := m.GetPin(0) 20 | if p0 == nil { 21 | t.Error("Pin 0 is expected to be defined") 22 | } 23 | 24 | // P9 should not exist 25 | p99 := m.GetPin(99) 26 | if p99 != nil { 27 | t.Error("Pin 99 should not exist") 28 | } 29 | } 30 | 31 | func TestGetPin(t *testing.T) { 32 | SetDriver(new(TestDriver)) 33 | 34 | p1, e := GetPin("P1") 35 | if e != nil { 36 | t.Error(fmt.Sprintf("GetPin('P1') should not return an error, returned '%s'", e)) 37 | } 38 | if p1 != 0 { 39 | t.Error("GetPin('P0') should return 0") 40 | } 41 | 42 | // test for altenate name of same pin 43 | p1a, e := GetPin("gpio1") 44 | if e != nil { 45 | t.Error(fmt.Sprintf("GetPin('gpio1') should not return an error, returned '%s'"), e) 46 | } 47 | if p1a != p1 { 48 | t.Error(fmt.Sprintf("Expected P1 and gpio1 to be the same pin")) 49 | } 50 | 51 | // test case insensitivity 52 | p1b, e := GetPin("GpIo1") 53 | if e != nil { 54 | t.Error(fmt.Sprintf("GetPin('GpIo1') should not return an error, returned '%s'"), e) 55 | } 56 | if p1b != p1 { 57 | t.Error(fmt.Sprintf("Expected P1 and GpIo1 to be the same pin")) 58 | } 59 | 60 | _, e = GetPin("P99") 61 | if e == nil { 62 | t.Error("GetPin('P99') should have returned an error but didn't") 63 | } 64 | } 65 | 66 | func TestPinMode(t *testing.T) { 67 | SetDriver(new(TestDriver)) 68 | 69 | gpio := getMockGPIO(t) 70 | 71 | // Set pin 0 to input. We expect no error as it's GPIO 72 | e := PinMode(0, INPUT) 73 | if e != nil { 74 | t.Error(fmt.Sprintf("GetPin('P1') should not return an error, returned '%s'", e)) 75 | } 76 | m := gpio.MockGetPinMode(0) 77 | if m != INPUT { 78 | t.Error("Pin set to read mode is not set in the driver") 79 | } 80 | 81 | // Change pin 0 to output. We expect no error in this case either, and the 82 | // new pin mode takes effect 83 | e = PinMode(0, OUTPUT) 84 | m = gpio.MockGetPinMode(0) 85 | if m != OUTPUT { 86 | t.Error("Pin changed from read to write mode is not set in the driver") 87 | } 88 | } 89 | 90 | func TestDigitalWrite(t *testing.T) { 91 | SetDriver(new(TestDriver)) 92 | 93 | gpio := getMockGPIO(t) 94 | 95 | pin2, _ := GetPin("p2") 96 | PinMode(pin2, OUTPUT) 97 | DigitalWrite(pin2, LOW) 98 | 99 | v := gpio.MockGetPinValue(pin2) 100 | if v != LOW { 101 | t.Error("After writing LOW to pin, driver should know this value") 102 | } 103 | 104 | DigitalWrite(pin2, HIGH) 105 | v = gpio.MockGetPinValue(pin2) 106 | if v != HIGH { 107 | t.Error("After writing HIGH to pin, driver should know this value") 108 | } 109 | } 110 | 111 | func TestDigitalRead(t *testing.T) { 112 | driver := new(TestDriver) 113 | SetDriver(driver) 114 | 115 | pin1, _ := GetPin("p1") 116 | 117 | PinMode(pin1, INPUT) 118 | writePinAndCheck(t, pin1, LOW, driver) 119 | writePinAndCheck(t, pin1, HIGH, driver) 120 | } 121 | 122 | func getMockGPIO(t *testing.T) *testGPIOModule { 123 | g, e := GetModule("gpio") 124 | if e != nil { 125 | t.Error(fmt.Sprintf("Fetching gpio module should not return an error, returned %s", e)) 126 | } 127 | if g == nil { 128 | t.Error("Could not get 'gpio' module") 129 | } 130 | 131 | return g.(*testGPIOModule) 132 | } 133 | 134 | func writePinAndCheck(t *testing.T, pin Pin, value int, driver *TestDriver) { 135 | gpio := getMockGPIO(t) 136 | 137 | gpio.MockSetPinValue(pin, value) 138 | v, e := DigitalRead(pin) 139 | if e != nil { 140 | t.Error("DigitalRead returned an error") 141 | } 142 | if v != value { 143 | t.Error(fmt.Sprintf("After writing %d to driver, DigitalRead method should return this value", value)) 144 | } 145 | } 146 | 147 | func TestBitManipulation(t *testing.T) { 148 | v := UInt16FromUInt8(0x45, 0x65) 149 | if v != 0x4565 { 150 | t.Error(fmt.Sprintf("UInt16FromUInt8 does not work correctly, expected 0x4565, got %04x", v)) 151 | } 152 | } 153 | 154 | func TestCpuInfo(t *testing.T) { 155 | s := CpuInfo(0, "processor") 156 | if s != "0" { 157 | t.Error(fmt.Sprintf("Expected 'processor' property of processor 0 to be 0 from CpuInfo, got '%s'", s)) 158 | } 159 | } 160 | 161 | func TestAnalogRead(t *testing.T) { 162 | SetDriver(new(TestDriver)) 163 | 164 | ap1, e := GetPin("p11") 165 | if e != nil { 166 | t.Error(fmt.Sprintf("GetPin('p11') should not return an error, returned '%s'", e)) 167 | } 168 | 169 | v, e := AnalogRead(ap1) 170 | if e != nil { 171 | t.Error(fmt.Sprintf("After reading from pin %d, got an unexpected error: %s", ap1, e)) 172 | } 173 | if v != 1 { 174 | t.Error(fmt.Sprintf("After reading from pin %d, did not get the expected value 1, got %d", ap1, v)) 175 | } 176 | 177 | ap2, _ := GetPin("p12") 178 | v, _ = AnalogRead(ap2) 179 | if e != nil { 180 | t.Error(fmt.Sprintf("After reading from pin %d, got an unexpected error: %s", ap2, e)) 181 | } 182 | if v != 1000 { 183 | t.Error(fmt.Sprintf("After reading from pin %d, did not get the expected value 1000, got %d", ap2, v)) 184 | } 185 | } 186 | 187 | func TestNoErrorCheck(t *testing.T) { 188 | SetDriver(new(TestDriver)) 189 | 190 | // @todo implement TestNoErrorCheck 191 | } 192 | -------------------------------------------------------------------------------- /module.go: -------------------------------------------------------------------------------- 1 | // Defines generic types and behaviours for modules. A given hardware platform will typically support one or more 2 | // modules. 3 | 4 | package hwio 5 | 6 | // Generic interface type for all modules. 7 | type Module interface { 8 | // Set parameters require to initialise the module. Generally should be called before Enable() is called, 9 | // but this may depend on the module. 10 | SetOptions(map[string]interface{}) error 11 | 12 | // enables the module for use. 13 | Enable() error 14 | 15 | // disables module and releases pins. 16 | Disable() error 17 | 18 | // Return the module name so it can be used for error reporting 19 | GetName() string 20 | } 21 | 22 | type GPIOModule interface { 23 | Module 24 | 25 | PinMode(pin Pin, mode PinIOMode) (e error) 26 | DigitalWrite(pin Pin, value int) (e error) 27 | DigitalRead(pin Pin) (result int, e error) 28 | ClosePin(pin Pin) (e error) 29 | } 30 | 31 | type PWMModule interface { 32 | Module 33 | 34 | EnablePin(pin Pin, enabled bool) error 35 | 36 | // Set the period of this pin, in nanoseconds 37 | SetPeriod(pin Pin, ns int64) error 38 | 39 | // Set the duty time, the amount of time during each period that that output is HIGH. 40 | SetDuty(pin Pin, ns int64) error 41 | } 42 | 43 | type AnalogModule interface { 44 | Module 45 | 46 | AnalogRead(pin Pin) (result int, e error) 47 | 48 | // read 49 | // reference voltage 50 | } 51 | 52 | // Interface for I2C implementations. Assumes that this device is the only bus master, so initiates all transactions. An I2C module 53 | // supports exactly one i2c bus, so for systems with multiple i2c busses, the driver will create an instance for each accessible 54 | // i2c bus. 55 | type I2CModule interface { 56 | Module 57 | 58 | GetDevice(address int) I2CDevice 59 | } 60 | 61 | // An object that represents a device on a bus. Once an i2c module has been enabled, you can use GetDevice to get an instance 62 | // of i2c device. You can then talk to the device directly with the supported operations. 63 | type I2CDevice interface { 64 | // Read a single byte from a register on the device. 65 | ReadByte(command byte) (byte, error) 66 | 67 | // Write a single byte to a register on the device. 68 | WriteByte(command byte, value byte) error 69 | 70 | // Read one or more bytes from the selected slave. 71 | Read(command byte, numBytes int) ([]byte, error) 72 | 73 | // Write one or more bytes to the selected slave. 74 | Write(command byte, buffer []byte) (e error) 75 | } 76 | 77 | // Interface for SPI implementations 78 | type SPIModule interface { 79 | Module 80 | 81 | // Select the device, and send data to it 82 | Write(slaveSelect int, data []byte) (e error) 83 | 84 | // Select the device, and read data from it 85 | Read(slaveSelect int, data []byte) (nBytes int, e error) 86 | } 87 | 88 | // Interface for controlling on-board LEDs, modelled on /sys/class/leds 89 | type LEDModule interface { 90 | Module 91 | 92 | GetLED(led string) (LEDModuleLED, error) 93 | } 94 | 95 | // LED from the an LEDModule 96 | type LEDModuleLED interface { 97 | SetTrigger(trigger string) error 98 | SetOn(on bool) error 99 | } 100 | -------------------------------------------------------------------------------- /module_bb_analog.go: -------------------------------------------------------------------------------- 1 | package hwio 2 | 3 | import ( 4 | "bytes" 5 | "errors" 6 | "fmt" 7 | "io/ioutil" 8 | "os" 9 | "strconv" 10 | "strings" 11 | ) 12 | 13 | // BBAnalogModule handles BeagleBone-specific analog. 14 | type BBAnalogModule struct { 15 | name string 16 | 17 | analogInitialised bool 18 | analogValueFilesPath string 19 | 20 | definedPins BBAnalogModulePinDefMap 21 | 22 | openPins map[Pin]*BBAnalogModuleOpenPin 23 | } 24 | 25 | // Represents the definition of an analog pin, which should contain all the info required to open, close, read and write the pin 26 | // using FS drivers. 27 | type BBAnalogModulePinDef struct { 28 | pin Pin 29 | analogLogical int 30 | } 31 | 32 | // A map of GPIO pin definitions. 33 | type BBAnalogModulePinDefMap map[Pin]*BBAnalogModulePinDef 34 | 35 | type BBAnalogModuleOpenPin struct { 36 | pin Pin 37 | analogLogical int 38 | 39 | // path to file representing analog pin 40 | analogFile string 41 | 42 | valueFile *os.File 43 | } 44 | 45 | func NewBBAnalogModule(name string) (result *BBAnalogModule) { 46 | result = &BBAnalogModule{name: name} 47 | result.openPins = make(map[Pin]*BBAnalogModuleOpenPin) 48 | return result 49 | } 50 | 51 | // Set options of the module. Parameters we look for include: 52 | // - "pins" - an object of type BBAnalogModulePinDefMap 53 | func (module *BBAnalogModule) SetOptions(options map[string]interface{}) error { 54 | v := options["pins"] 55 | if v == nil { 56 | return fmt.Errorf("Module '%s' SetOptions() did not get 'pins' values", module.GetName()) 57 | } 58 | 59 | module.definedPins = v.(BBAnalogModulePinDefMap) 60 | return nil 61 | } 62 | 63 | // enable GPIO module. It doesn't allocate any pins immediately. 64 | func (module *BBAnalogModule) Enable() error { 65 | // once-off initialisation of analog 66 | if !module.analogInitialised { 67 | path, e := findFirstMatchingFile("/sys/devices/bone_capemgr.*/slots") 68 | if e != nil { 69 | return e 70 | } 71 | 72 | // determine if cape-bone-iio is already in the file. If so, we've already initialised it. 73 | if !module.hasCapeBoneIIO(path) { 74 | // enable analog 75 | e = WriteStringToFile(path, "cape-bone-iio") 76 | if e != nil { 77 | return e 78 | } 79 | } 80 | 81 | // determine path where analog files are 82 | path, e = findFirstMatchingFile("/sys/devices/ocp.*/helper.*/AIN0") 83 | if e != nil { 84 | return e 85 | } 86 | if path == "" { 87 | return errors.New("Could not locate /sys/devices/ocp.*/helper.*/AIN0") 88 | } 89 | 90 | // remove AIN0 to get the path where these files are 91 | module.analogValueFilesPath = strings.TrimSuffix(path, "AIN0") 92 | 93 | module.analogInitialised = true 94 | 95 | // attempt to assign all pins to this module 96 | for pin, _ := range module.definedPins { 97 | // attempt to assign this pin for this module. 98 | e = AssignPin(pin, module) 99 | if e != nil { 100 | return e 101 | } 102 | } 103 | } 104 | return nil 105 | } 106 | 107 | func (module *BBAnalogModule) hasCapeBoneIIO(path string) bool { 108 | f, e := ioutil.ReadFile(path) 109 | if e != nil { 110 | return false 111 | } 112 | if bytes.Contains(f, []byte("cape-bone-iio")) { 113 | return true 114 | } 115 | return false 116 | } 117 | 118 | // disables module and release any pins assigned. 119 | func (module *BBAnalogModule) Disable() error { 120 | // Unassign any pins we may have assigned 121 | for pin, _ := range module.definedPins { 122 | // attempt to assign this pin for this module. 123 | UnassignPin(pin) 124 | } 125 | 126 | // if there are any open analog pins, close them 127 | for _, openPin := range module.openPins { 128 | openPin.analogClose() 129 | } 130 | return nil 131 | } 132 | 133 | func (module *BBAnalogModule) GetName() string { 134 | return module.name 135 | } 136 | 137 | // func (module *BBAnalogModule) AnalogWrite(pin Pin, value int) (e error) { 138 | // return nil 139 | // } 140 | 141 | func (module *BBAnalogModule) AnalogRead(pin Pin) (int, error) { 142 | var e error 143 | 144 | // Get it if it's already open 145 | openPin := module.openPins[pin] 146 | if openPin == nil { 147 | // If it's not open yet, open on demand 148 | openPin, e = module.makeOpenAnalogPin(pin) 149 | // return 0, errors.New("Pin is being read for analog value but has not been opened. Have you called PinMode?") 150 | if e != nil { 151 | return 0, e 152 | } 153 | } 154 | return openPin.analogGetValue() 155 | } 156 | 157 | func (module *BBAnalogModule) makeOpenAnalogPin(pin Pin) (*BBAnalogModuleOpenPin, error) { 158 | p := module.definedPins[pin] 159 | if p == nil { 160 | return nil, fmt.Errorf("Pin %d is not known to analog module", pin) 161 | } 162 | 163 | path := module.analogValueFilesPath + fmt.Sprintf("AIN%d", p.analogLogical) 164 | result := &BBAnalogModuleOpenPin{pin: pin, analogLogical: p.analogLogical, analogFile: path} 165 | e := result.analogOpen() 166 | if e != nil { 167 | return nil, e 168 | } 169 | 170 | module.openPins[pin] = result 171 | 172 | return result, nil 173 | } 174 | 175 | func (op *BBAnalogModuleOpenPin) analogOpen() error { 176 | // Open analog input file computed from the calculated path of actual analog files and the analog pin name 177 | f, e := os.OpenFile(op.analogFile, os.O_RDONLY, 0666) 178 | op.valueFile = f 179 | 180 | return e 181 | } 182 | 183 | func (op *BBAnalogModuleOpenPin) analogGetValue() (int, error) { 184 | var b []byte 185 | b = make([]byte, 5) 186 | n, e := op.valueFile.ReadAt(b, 0) 187 | 188 | // if there's an error and no byte were read, quit now. If we didn't get all the bytes we asked for, which 189 | // is generally the case, we will get an error as well but would have got some bytes. 190 | if e != nil && n == 0 { 191 | return 0, e 192 | } 193 | 194 | value, e := strconv.Atoi(string(b[:n-1])) 195 | 196 | return value, e 197 | } 198 | 199 | func (op *BBAnalogModuleOpenPin) analogClose() error { 200 | return op.valueFile.Close() 201 | } 202 | -------------------------------------------------------------------------------- /module_bb_pwm.go: -------------------------------------------------------------------------------- 1 | // Implementation of PWM module interface for systems using device tree. 2 | // It follows a similar pattern as the DT GPIO module. A module instance can handle 3 | // multiple pins. 4 | 5 | // period = nanoseconds, 1,000,000,000 is a second 6 | // duty = active period 7 | // polarity = 0: duty high; polarity = 1: duty low 8 | // run = 0: disable; 1: enabled 9 | 10 | package hwio 11 | 12 | // References: 13 | // - http://digital-drive.com/?p=146 14 | 15 | import ( 16 | "bufio" 17 | "fmt" 18 | "os" 19 | "strconv" 20 | "strings" 21 | ) 22 | 23 | type BBPWMModule struct { 24 | name string 25 | definedPins BBPWMModulePinDefMap 26 | openPins map[Pin]*BBPWMModuleOpenPin 27 | } 28 | 29 | type BBPWMModulePinDef struct { 30 | pin Pin 31 | 32 | // used to derive the slot if not there, and the folder which contains the PWM files. This is of the form 33 | // "P8_13" and is case-sensitive 34 | name string 35 | } 36 | 37 | type BBPWMModulePinDefMap map[Pin]*BBPWMModulePinDef 38 | 39 | type BBPWMModuleOpenPin struct { 40 | pin Pin 41 | periodFile string 42 | dutyFile string 43 | polarityFile string 44 | runFile string 45 | } 46 | 47 | func (pinDef BBPWMModulePinDef) overlayName() string { 48 | return "bone_pwm_" + pinDef.name 49 | } 50 | 51 | func (pinDef BBPWMModulePinDef) deviceDir() string { 52 | s, _ := findFirstMatchingFile("/sys/devices/ocp.*/pwm_test_" + pinDef.name + ".*") 53 | return s + "/" 54 | } 55 | 56 | func NewBBPWMModule(name string) (result *BBPWMModule) { 57 | result = &BBPWMModule{name: name} 58 | result.openPins = make(map[Pin]*BBPWMModuleOpenPin) 59 | return result 60 | } 61 | 62 | // Set options of the module. Parameters we look for include: 63 | // - "pins" - an object of type DTGPIOModulePinDefMap 64 | func (module *BBPWMModule) SetOptions(options map[string]interface{}) error { 65 | v := options["pins"] 66 | if v == nil { 67 | return fmt.Errorf("Module '%s' SetOptions() did not get 'pins' values", module.GetName()) 68 | } 69 | 70 | module.definedPins = v.(BBPWMModulePinDefMap) 71 | return nil 72 | } 73 | 74 | // enable PWM module. It doesn't allocate any pins immediately. It does check of am33xx_pwm is present 75 | // in the capemgr slots, and adds it if not. By default, this is not enabled on the BB but can be added 76 | // easily. 77 | func (module *BBPWMModule) Enable() error { 78 | // ensure that the PWM module is loaded 79 | return module.ensureSlot("am33xx_pwm") 80 | } 81 | 82 | // disables module and release any pins assigned. 83 | func (module *BBPWMModule) Disable() error { 84 | for _, openPin := range module.openPins { 85 | openPin.closePin() 86 | } 87 | return nil 88 | } 89 | 90 | func (module *BBPWMModule) GetName() string { 91 | return module.name 92 | } 93 | 94 | // Enable a specific PWM pin. You need to call this explicitly after enabling the module, as the 95 | // module will not by default allocate all pins, since there are a few. 96 | func (module *BBPWMModule) EnablePin(pin Pin, enabled bool) error { 97 | if module.definedPins[pin] == nil { 98 | return fmt.Errorf("Pin %d is not known as a PWM pin on module %s", pin, module.GetName()) 99 | } 100 | 101 | openPin := module.openPins[pin] 102 | if enabled { 103 | // ensure pin is enabled by creating an open pin 104 | if openPin == nil { 105 | p, e := module.makeOpenPin(pin) 106 | if e != nil { 107 | return e 108 | } 109 | module.openPins[pin] = p 110 | return p.enabled(true) 111 | } 112 | } else { 113 | // disable the pin if enabled 114 | if openPin != nil { 115 | return openPin.enabled(false) 116 | } 117 | } 118 | return nil 119 | } 120 | 121 | // Set the period of this pin, in nanoseconds 122 | func (module *BBPWMModule) SetPeriod(pin Pin, ns int64) error { 123 | openPin := module.openPins[pin] 124 | if openPin == nil { 125 | return fmt.Errorf("PWM pin is being written but is not enabled. Have you called EnablePin?") 126 | } 127 | 128 | return openPin.setPeriod(ns) 129 | } 130 | 131 | // Set the duty time, the amount of time during each period that that output is HIGH. 132 | func (module *BBPWMModule) SetDuty(pin Pin, ns int64) error { 133 | openPin := module.openPins[pin] 134 | if openPin == nil { 135 | return fmt.Errorf("PWM pin is being written but is not enabled. Have you called EnablePin?") 136 | } 137 | 138 | return openPin.setDuty(ns) 139 | } 140 | 141 | // create an openPin object and put it in the map. 142 | func (module *BBPWMModule) makeOpenPin(pin Pin) (*BBPWMModuleOpenPin, error) { 143 | p := module.definedPins[pin] 144 | if p == nil { 145 | return nil, fmt.Errorf("Pin %d is not known to PWM module %s", pin, module.GetName()) 146 | } 147 | 148 | e := AssignPin(pin, module) 149 | if e != nil { 150 | return nil, e 151 | } 152 | 153 | // Ensure that the cape manager knows about it 154 | e = module.ensureSlot(p.overlayName()) 155 | if e != nil { 156 | return nil, e 157 | } 158 | 159 | dir := p.deviceDir() 160 | result := &BBPWMModuleOpenPin{pin: pin} 161 | result.periodFile = dir + "period" 162 | result.dutyFile = dir + "duty" 163 | result.runFile = dir + "run" 164 | result.polarityFile = dir + "polarity" 165 | 166 | module.openPins[pin] = result 167 | 168 | // ensure polarity is 0, so that the duty time represents the time the signal is high. 169 | e = WriteStringToFile(result.polarityFile, "0") 170 | if e != nil { 171 | return nil, e 172 | } 173 | 174 | return result, nil 175 | } 176 | 177 | // Add the named thing to the capemanager slots file. 178 | // @todo refactor for beaglebone black driver, and refactor analog as well which does the same thing. 179 | func (module *BBPWMModule) ensureSlot(item string) error { 180 | path, e := findFirstMatchingFile("/sys/devices/bone_capemgr.*/slots") 181 | if e != nil { 182 | return e 183 | } 184 | 185 | file, e := os.Open(path) 186 | if e != nil { 187 | return e 188 | } 189 | 190 | scanner := bufio.NewScanner(file) 191 | for scanner.Scan() { 192 | line := scanner.Text() 193 | if strings.Index(line, item) > 0 { 194 | return nil 195 | } 196 | } 197 | 198 | // enable the item 199 | e = WriteStringToFile(path, item) 200 | // delay a little as it seems to take a bit of time set enable the slot 201 | Delay(100) 202 | return e 203 | } 204 | 205 | // Needs to be called to allocate the GPIO pin 206 | func (op *BBPWMModuleOpenPin) closePin() error { 207 | // @todo how do we close this pin? 208 | 209 | return nil 210 | } 211 | 212 | // @todo capture the stdout message on writestring, which happens if the driver doesn't like the value. 213 | // Set the period in nanoseconds. On BBB, maximum is 1 second (1,000,000,000ns) 214 | func (op *BBPWMModuleOpenPin) setPeriod(ns int64) error { 215 | s := strconv.FormatInt(int64(ns), 10) 216 | e := WriteStringToFile(op.periodFile, s) 217 | if e != nil { 218 | return e 219 | } 220 | 221 | return nil 222 | } 223 | 224 | func (op *BBPWMModuleOpenPin) setDuty(ns int64) error { 225 | s := strconv.FormatInt(int64(ns), 10) 226 | e := WriteStringToFile(op.dutyFile, s) 227 | if e != nil { 228 | return e 229 | } 230 | 231 | return nil 232 | } 233 | 234 | func (op *BBPWMModuleOpenPin) enabled(e bool) error { 235 | if e { 236 | return WriteStringToFile(op.runFile, "1") 237 | } else { 238 | return WriteStringToFile(op.runFile, "0") 239 | } 240 | } 241 | -------------------------------------------------------------------------------- /module_dtgpio.go: -------------------------------------------------------------------------------- 1 | // A GPIO module that uses Linux 3.7+ file system drivers with device tree. This module is intended to work for any 3.7+ configuration, 2 | // including BeagleBone Black and Raspberry Pi's with new kernels. The actual pin configuration is passed through on SetOptions. 3 | 4 | package hwio 5 | 6 | import ( 7 | "errors" 8 | "fmt" 9 | "os" 10 | "strconv" 11 | ) 12 | 13 | type DTGPIOModule struct { 14 | name string 15 | definedPins DTGPIOModulePinDefMap 16 | openPins map[Pin]*DTGPIOModuleOpenPin 17 | } 18 | 19 | // Represents the definition of a GPIO pin, which should contain all the info required to open, close, read and write the pin 20 | // using FS drivers. 21 | type DTGPIOModulePinDef struct { 22 | pin Pin 23 | gpioLogical int 24 | } 25 | 26 | // A map of GPIO pin definitions. 27 | type DTGPIOModulePinDefMap map[Pin]*DTGPIOModulePinDef 28 | 29 | type DTGPIOModuleOpenPin struct { 30 | pin Pin 31 | gpioLogical int 32 | gpioBaseName string 33 | valueFile *os.File 34 | } 35 | 36 | func NewDTGPIOModule(name string) (result *DTGPIOModule) { 37 | result = &DTGPIOModule{name: name} 38 | result.openPins = make(map[Pin]*DTGPIOModuleOpenPin) 39 | return result 40 | } 41 | 42 | // Set options of the module. Parameters we look for include: 43 | // - "pins" - an object of type DTGPIOModulePinDefMap 44 | func (module *DTGPIOModule) SetOptions(options map[string]interface{}) error { 45 | v := options["pins"] 46 | if v == nil { 47 | return fmt.Errorf("Module '%s' SetOptions() did not get 'pins' values", module.GetName()) 48 | } 49 | 50 | module.definedPins = v.(DTGPIOModulePinDefMap) 51 | return nil 52 | } 53 | 54 | // enable GPIO module. It doesn't allocate any pins immediately. 55 | func (module *DTGPIOModule) Enable() error { 56 | return nil 57 | } 58 | 59 | // disables module and release any pins assigned. 60 | func (module *DTGPIOModule) Disable() error { 61 | for _, openPin := range module.openPins { 62 | openPin.gpioUnexport() 63 | } 64 | return nil 65 | } 66 | 67 | func (module *DTGPIOModule) GetName() string { 68 | return module.name 69 | } 70 | 71 | func (module *DTGPIOModule) PinMode(pin Pin, mode PinIOMode) error { 72 | if module.definedPins[pin] == nil { 73 | return fmt.Errorf("Pin %d is not known as a GPIO pin", pin) 74 | } 75 | 76 | // attempt to assign this pin for this module. 77 | e := AssignPin(pin, module) 78 | if e != nil { 79 | return e 80 | } 81 | 82 | // Create an open pin object 83 | openPin, e := module.makeOpenGPIOPin(pin) 84 | if e != nil { 85 | return e 86 | } 87 | 88 | e = openPin.gpioExport() 89 | if e != nil { 90 | return e 91 | } 92 | 93 | if mode == OUTPUT { 94 | fmt.Printf("about to set pin %d to output\n", pin) 95 | e = openPin.gpioDirection("out") 96 | if e != nil { 97 | return e 98 | } 99 | } else { 100 | e = openPin.gpioDirection("in") 101 | // @todo implement pull up and pull down support 102 | 103 | // pull := BB_CONF_PULL_DISABLE 104 | // // note: pull up/down modes assume that CONF_PULLDOWN resets the pull disable bit 105 | // if mode == INPUT_PULLUP { 106 | // pull = BB_CONF_PULLUP 107 | // } else if mode == INPUT_PULLDOWN { 108 | // pull = BB_CONF_PULLDOWN 109 | // } 110 | 111 | if e != nil { 112 | return e 113 | } 114 | } 115 | return nil 116 | } 117 | 118 | func (module *DTGPIOModule) DigitalWrite(pin Pin, value int) (e error) { 119 | openPin := module.openPins[pin] 120 | if openPin == nil { 121 | return errors.New("Pin is being written but has not been opened. Have you called PinMode?") 122 | } 123 | // if a.pinIOMode != OUTPUT { 124 | // return errors.New(fmt.Sprintf("DigitalWrite: pin %d mode is not set for output", pin)) 125 | // } 126 | openPin.gpioSetValue(value) 127 | return nil 128 | } 129 | 130 | func (module *DTGPIOModule) DigitalRead(pin Pin) (value int, e error) { 131 | openPin := module.openPins[pin] 132 | if openPin == nil { 133 | return 0, errors.New("Pin is being read from but has not been opened. Have you called PinMode?") 134 | } 135 | // if a.pinIOMode != INPUT && a.pinIOMode != INPUT_PULLUP && a.pinIOMode != INPUT_PULLDOWN { 136 | // e = errors.New(fmt.Sprintf("DigitalRead: pin %d mode not set for input", pin)) 137 | // return 138 | // } 139 | 140 | return openPin.gpioGetValue() 141 | } 142 | 143 | func (module *DTGPIOModule) ClosePin(pin Pin) error { 144 | openPin := module.openPins[pin] 145 | if openPin == nil { 146 | return errors.New("Pin is being closed but has not been opened. Have you called PinMode?") 147 | } 148 | e := openPin.gpioUnexport() 149 | if e != nil { 150 | return e 151 | } 152 | return UnassignPin(pin) 153 | } 154 | 155 | // create an openPin object and put it in the map. 156 | func (module *DTGPIOModule) makeOpenGPIOPin(pin Pin) (*DTGPIOModuleOpenPin, error) { 157 | p := module.definedPins[pin] 158 | if p == nil { 159 | return nil, fmt.Errorf("Pin %d is not known to GPIO module", pin) 160 | } 161 | 162 | result := &DTGPIOModuleOpenPin{pin: pin, gpioLogical: p.gpioLogical} 163 | module.openPins[pin] = result 164 | 165 | return result, nil 166 | } 167 | 168 | // For GPIO: 169 | // - write GPIO pin to /sys/class/gpio/export. This is the port number plus pin on that port. Ports 0, 32, 64, 96. In our case, gpioLogical 170 | // contains this value. 171 | // - write direction to /sys/class/gpio/gpio{nn}/direction. Values are 'in' and 'out' 172 | 173 | // Needs to be called to allocate the GPIO pin 174 | func (op *DTGPIOModuleOpenPin) gpioExport() error { 175 | bn := "/sys/class/gpio/gpio" + strconv.Itoa(op.gpioLogical) 176 | if !fileExists(bn) { 177 | s := strconv.FormatInt(int64(op.gpioLogical), 10) 178 | e := WriteStringToFile("/sys/class/gpio/export", s) 179 | if e != nil { 180 | return e 181 | } 182 | } 183 | 184 | // calculate the base name for the gpio pin 185 | op.gpioBaseName = bn 186 | return nil 187 | } 188 | 189 | // Needs to be called to allocate the GPIO pin 190 | func (op *DTGPIOModuleOpenPin) gpioUnexport() error { 191 | s := strconv.FormatInt(int64(op.gpioLogical), 10) 192 | e := WriteStringToFile("/sys/class/gpio/unexport", s) 193 | if e != nil { 194 | return e 195 | } 196 | 197 | return nil 198 | } 199 | 200 | // Once exported, the direction of a GPIO can be set 201 | func (op *DTGPIOModuleOpenPin) gpioDirection(dir string) error { 202 | if dir != "in" && dir != "out" { 203 | return errors.New("direction must be in or out") 204 | } 205 | f := op.gpioBaseName + "/direction" 206 | e := WriteStringToFile(f, dir) 207 | 208 | mode := os.O_WRONLY | os.O_TRUNC 209 | if dir == "in" { 210 | mode = os.O_RDONLY 211 | } 212 | 213 | // open the value file with the correct mode. Put that file in 'op'. Note that we keep this file open 214 | // continuously for performance. 215 | // Preliminary tests on 200,000 DigitalWrites indicate an order of magnitude improvement when we don't have 216 | // to re-open the file each time. Re-seeking and writing a new value suffices. 217 | op.valueFile, e = os.OpenFile(op.gpioBaseName+"/value", mode, 0666) 218 | 219 | return e 220 | } 221 | 222 | // Get the value. Will return HIGH or LOW 223 | func (op *DTGPIOModuleOpenPin) gpioGetValue() (int, error) { 224 | var b []byte 225 | b = make([]byte, 1) 226 | n, e := op.valueFile.ReadAt(b, 0) 227 | 228 | value := 0 229 | if n > 0 { 230 | if b[0] == '1' { 231 | value = HIGH 232 | } else { 233 | value = LOW 234 | } 235 | } 236 | return value, e 237 | } 238 | 239 | // Set the value, Expects HIGH or LOW 240 | func (op *DTGPIOModuleOpenPin) gpioSetValue(value int) error { 241 | if op.valueFile == nil { 242 | fmt.Printf("value file no set\n") 243 | return errors.New("value file is not defined") 244 | } 245 | 246 | // Seek the start of the value file before writing. This is sufficient for the driver to accept a new value. 247 | _, e := op.valueFile.Seek(0, 0) 248 | if e != nil { 249 | return e 250 | } 251 | 252 | // Write a 1 or 0. 253 | // @todo investigate if we'd get better performance if we have precalculated []byte values with 0 and 1, and 254 | // use write directly instead of WriteString. Probably only marginal. 255 | // @todo also check out http://hackaday.com/2013/12/07/speeding-up-beaglebone-black-gpio-a-thousand-times/ 256 | if value == 0 { 257 | op.valueFile.WriteString("0") 258 | } else { 259 | op.valueFile.WriteString("1") 260 | } 261 | 262 | return nil 263 | } 264 | -------------------------------------------------------------------------------- /module_dti2c.go: -------------------------------------------------------------------------------- 1 | // Implementation of I2C module interface for systems using device tree. 2 | 3 | package hwio 4 | 5 | // references: 6 | // - http://datko.net/2013/11/03/bbb_i2c/ 7 | // - http://elinux.org/Interfacing_with_I2C_Devices 8 | // - http://grokbase.com/t/gg/golang-nuts/1296kz4tkg/go-nuts-how-to-call-linux-kernel-method-i2c-smbus-write-byte-from-go 9 | // - http://learn.adafruit.com/setting-up-io-python-library-on-beaglebone-black/i2c 10 | // - https://bitbucket.org/gmcbay/i2c/src/1235f1776ee749f0eaeb6de69d8804a6dd70d9d5/i2c_bus.go?at=master 11 | // - http://derekmolloy.ie/beaglebone/beaglebone-an-i2c-tutorial-interfacing-to-a-bma180-accelerometer/' 12 | 13 | import ( 14 | "fmt" 15 | "os" 16 | "sync" 17 | "syscall" 18 | "unsafe" 19 | ) 20 | 21 | // A list of the pins that are allocated when the bus is enabled. We don't need to know what these mean within the 22 | // module. We just need them so we can mark them allocated. 23 | type DTI2CModulePins []Pin 24 | 25 | type DTI2CModule struct { 26 | sync.Mutex 27 | 28 | name string 29 | deviceFile string 30 | definedPins DTI2CModulePins 31 | 32 | // File used to represent the bus once it's opened 33 | fd *os.File 34 | } 35 | 36 | // Data that is passed to/from ioctl calls 37 | type i2c_smbus_ioctl_data struct { 38 | read_write uint8 39 | command uint8 40 | size int 41 | data uintptr 42 | } 43 | 44 | // Constants used by ioctl, from i2c-dev.h 45 | const ( 46 | I2C_SMBUS_READ = 1 47 | I2C_SMBUS_WRITE = 0 48 | I2C_SMBUS_BYTE_DATA = 2 49 | I2C_SMBUS_I2C_BLOCK_DATA = 8 50 | I2C_SMBUS_BLOCK_MAX = 32 51 | 52 | // Talk to bus 53 | I2C_SMBUS = 0x0720 54 | 55 | // Set bus slave 56 | I2C_SLAVE = 0x0703 57 | ) 58 | 59 | func NewDTI2CModule(name string) (result *DTI2CModule) { 60 | result = &DTI2CModule{name: name} 61 | return result 62 | } 63 | 64 | // Accept options for the I2C module. Expected options include: 65 | // - "device" - a string that identifies the device file, e.g. "/dev/i2c-1". 66 | // - "pins" - an object of type DTI2CModulePins that identifies the pins that will be assigned 67 | // when this module is enabled. 68 | func (module *DTI2CModule) SetOptions(options map[string]interface{}) error { 69 | // get the device 70 | vd := options["device"] 71 | if vd == nil { 72 | return fmt.Errorf("Module '%s' SetOptions() did not get 'device' value", module.GetName()) 73 | } 74 | 75 | module.deviceFile = vd.(string) 76 | 77 | // get the pins 78 | vp := options["pins"] 79 | if vp == nil { 80 | return fmt.Errorf("Module '%s' SetOptions() did not get 'pins' values", module.GetName()) 81 | } 82 | 83 | module.definedPins = vp.(DTI2CModulePins) 84 | 85 | return nil 86 | } 87 | 88 | // enable this I2C module 89 | func (module *DTI2CModule) Enable() error { 90 | // Assign the pins so nothing else can allocate them. 91 | for _, pin := range module.definedPins { 92 | // fmt.Printf("assigning pin %d\n", pin) 93 | AssignPin(pin, module) 94 | } 95 | 96 | // @todo consider lazily opening the file. Since Enable is called automatically by BBB driver, this 97 | // @todo file will always be open even if i2c is not used. 98 | fd, e := os.OpenFile(module.deviceFile, os.O_RDWR, os.ModeExclusive) 99 | if e != nil { 100 | return e 101 | } 102 | module.fd = fd 103 | 104 | return nil 105 | } 106 | 107 | // disables module and release any pins assigned. 108 | func (module *DTI2CModule) Disable() error { 109 | if e := module.fd.Close(); e != nil { 110 | return e 111 | } 112 | 113 | for _, pin := range module.definedPins { 114 | UnassignPin(pin) 115 | } 116 | 117 | return nil 118 | } 119 | 120 | func (module *DTI2CModule) GetName() string { 121 | return module.name 122 | } 123 | 124 | func (module *DTI2CModule) GetDevice(address int) I2CDevice { 125 | return NewDTI2CDevice(module, address) 126 | } 127 | 128 | type DTI2CDevice struct { 129 | module *DTI2CModule 130 | address int 131 | } 132 | 133 | func NewDTI2CDevice(module *DTI2CModule, address int) *DTI2CDevice { 134 | return &DTI2CDevice{module, address} 135 | } 136 | 137 | func (device *DTI2CDevice) Write(command byte, data []byte) (e error) { 138 | device.module.Lock() 139 | defer device.module.Unlock() 140 | 141 | device.sendSlaveAddress() 142 | 143 | buffer := make([]byte, len(data)+1) 144 | buffer[0] = byte(len(data)) 145 | copy(buffer[1:], data) 146 | 147 | // buffer := make([]byte, numBytes+2) 148 | 149 | busData := i2c_smbus_ioctl_data{ 150 | read_write: I2C_SMBUS_WRITE, 151 | command: command, 152 | size: I2C_SMBUS_I2C_BLOCK_DATA, 153 | data: uintptr(unsafe.Pointer(&buffer[0])), 154 | } 155 | 156 | _, _, err := syscall.Syscall(syscall.SYS_IOCTL, uintptr(device.module.fd.Fd()), I2C_SMBUS, uintptr(unsafe.Pointer(&busData))) 157 | if err != 0 { 158 | return syscall.Errno(err) 159 | } 160 | 161 | return nil 162 | } 163 | 164 | func (device *DTI2CDevice) Read(command byte, numBytes int) ([]byte, error) { 165 | device.module.Lock() 166 | defer device.module.Unlock() 167 | 168 | device.sendSlaveAddress() 169 | 170 | buffer := make([]byte, numBytes+1) 171 | buffer[0] = byte(numBytes) 172 | 173 | // buffer := make([]byte, numBytes+2) 174 | 175 | busData := i2c_smbus_ioctl_data{ 176 | read_write: I2C_SMBUS_READ, 177 | command: command, 178 | size: I2C_SMBUS_I2C_BLOCK_DATA, 179 | data: uintptr(unsafe.Pointer(&buffer[0])), 180 | } 181 | 182 | _, _, err := syscall.Syscall(syscall.SYS_IOCTL, uintptr(device.module.fd.Fd()), I2C_SMBUS, uintptr(unsafe.Pointer(&busData))) 183 | if err != 0 { 184 | return nil, syscall.Errno(err) 185 | } 186 | 187 | result := make([]byte, numBytes) 188 | copy(result, buffer[1:]) 189 | 190 | return result, nil 191 | } 192 | 193 | // Read 1 byte from the bus 194 | func (device *DTI2CDevice) ReadByte(command byte) (byte, error) { 195 | device.module.Lock() 196 | defer device.module.Unlock() 197 | 198 | e := device.sendSlaveAddress() 199 | if e != nil { 200 | return 0, e 201 | } 202 | 203 | data := uint8(0) 204 | 205 | busData := i2c_smbus_ioctl_data{ 206 | read_write: I2C_SMBUS_READ, 207 | command: command, 208 | size: I2C_SMBUS_BYTE_DATA, 209 | data: uintptr(unsafe.Pointer(&data)), 210 | } 211 | 212 | _, _, err := syscall.Syscall(syscall.SYS_IOCTL, uintptr(device.module.fd.Fd()), I2C_SMBUS, uintptr(unsafe.Pointer(&busData))) 213 | if err != 0 { 214 | return 0, syscall.Errno(err) 215 | } 216 | 217 | return data, nil 218 | } 219 | 220 | func (device *DTI2CDevice) WriteByte(command byte, value byte) error { 221 | device.module.Lock() 222 | defer device.module.Unlock() 223 | 224 | e := device.sendSlaveAddress() 225 | if e != nil { 226 | return e 227 | } 228 | 229 | busData := i2c_smbus_ioctl_data{ 230 | read_write: I2C_SMBUS_WRITE, 231 | command: command, 232 | size: I2C_SMBUS_BYTE_DATA, 233 | data: uintptr(unsafe.Pointer(&value)), 234 | } 235 | 236 | _, _, err := syscall.Syscall(syscall.SYS_IOCTL, uintptr(device.module.fd.Fd()), I2C_SMBUS, uintptr(unsafe.Pointer(&busData))) 237 | if err != 0 { 238 | return syscall.Errno(err) 239 | } 240 | 241 | return nil 242 | } 243 | 244 | func (device *DTI2CDevice) sendSlaveAddress() error { 245 | _, _, enum := syscall.Syscall(syscall.SYS_IOCTL, uintptr(device.module.fd.Fd()), I2C_SLAVE, uintptr(device.address)) 246 | if enum != 0 { 247 | return fmt.Errorf("Could not open I2C bus on module %s", device.module.GetName()) 248 | } 249 | return nil 250 | } 251 | -------------------------------------------------------------------------------- /module_dtleds.go: -------------------------------------------------------------------------------- 1 | package hwio 2 | 3 | import ( 4 | "errors" 5 | "fmt" 6 | "strings" 7 | ) 8 | 9 | // This is a module to support the onboard LED functions. While these are actually attached to GPIO pins that 10 | // are not exposed on the expansion headers, we can't use GPIO, as a driver is present that provides ways 11 | // to map what is displayed on the LEDs. 12 | type ( 13 | DTLEDModule struct { 14 | name string 15 | definedPins DTLEDModulePins 16 | 17 | leds map[string]*DTLEDModuleLED 18 | } 19 | 20 | DTLEDModuleLED struct { 21 | path string 22 | currentTrigger string 23 | } 24 | 25 | // A map of pin names (e.g. "USR0") to their path e.g. /sys/class/leds/{led}/ 26 | DTLEDModulePins map[string]string 27 | ) 28 | 29 | func NewDTLEDModule(name string) *DTLEDModule { 30 | return &DTLEDModule{name: name, leds: make(map[string]*DTLEDModuleLED)} 31 | } 32 | 33 | func (m *DTLEDModule) Enable() error { 34 | return nil 35 | } 36 | 37 | func (m *DTLEDModule) Disable() error { 38 | return nil 39 | } 40 | 41 | func (m *DTLEDModule) GetName() string { 42 | return m.name 43 | } 44 | 45 | func (m *DTLEDModule) SetOptions(options map[string]interface{}) error { 46 | // get the pins 47 | if p := options["pins"]; p != "" { 48 | m.definedPins = p.(DTLEDModulePins) 49 | 50 | return nil 51 | } else { 52 | return fmt.Errorf("Module '%s' SetOptions() did not get 'pins' value", m.GetName()) 53 | } 54 | 55 | } 56 | 57 | // Get a LED to manipulate. 'led' must be 0 to 3. 58 | func (m *DTLEDModule) GetLED(led string) (LEDModuleLED, error) { 59 | led = strings.ToLower(led) 60 | 61 | if ol := m.leds[led]; ol != nil { 62 | return ol, nil 63 | } 64 | 65 | if pin := m.definedPins[led]; pin != "" { 66 | result := &DTLEDModuleLED{} 67 | result.path = pin 68 | result.currentTrigger = "" 69 | m.leds[led] = result 70 | return result, nil 71 | } else { 72 | return nil, fmt.Errorf("GetLED: invalid led '%s'", led) 73 | } 74 | } 75 | 76 | // Set the trigger for the LED. The values come from /sys/class/leds/*/trigger. This tells the driver what should be displayed on the 77 | // LED. The useful values include: 78 | // - none The LED can be set up programmatic control. If you want to turn a LED on and off yourself, you want 79 | // this mode. 80 | // - nand-disk Automatically displays nand disk activity 81 | // - mmc0 Show MMC0 activity. 82 | // - mmc1 Show MMC1 activity. By default, USR3 is configured for mmc1. 83 | // - timer 84 | // - heartbeat Show a heartbeat for system functioning. By default, USR0 is configured for heartbeat. 85 | // - cpu0 Show CPU activity. By default, USR2 is configured for cpu0. 86 | // For BeagleBone black system defaults (at least for Angstrom are): 87 | // - USR0: heartbeat 88 | // - USR1: mmc0 89 | // - USR2: cpu0 90 | // - USR3: mmc1 91 | // For Raspberry Pi is mmc0. 92 | func (led *DTLEDModuleLED) SetTrigger(trigger string) error { 93 | led.currentTrigger = trigger 94 | return WriteStringToFile(led.path+"trigger", trigger) 95 | } 96 | 97 | func (led *DTLEDModuleLED) SetOn(on bool) error { 98 | if led.currentTrigger != "none" { 99 | return errors.New("LED SetOn requires that the LED trigger has been set to 'none'") 100 | } 101 | 102 | v := "0" 103 | if on { 104 | v = "1" 105 | } 106 | 107 | return WriteStringToFile(led.path+"brightness", v) 108 | } 109 | -------------------------------------------------------------------------------- /module_odroidc1_analog.go: -------------------------------------------------------------------------------- 1 | package hwio 2 | 3 | import ( 4 | "errors" 5 | "fmt" 6 | "os" 7 | "strconv" 8 | ) 9 | 10 | // ODroidC1AnalogModule is a module for handling the Odroid C1 analog hardware, which is not generic. 11 | type ODroidC1AnalogModule struct { 12 | name string 13 | 14 | analogInitialised bool 15 | 16 | definedPins ODroidC1AnalogModulePinDefMap 17 | 18 | openPins map[Pin]*ODroidC1AnalogModuleOpenPin 19 | } 20 | 21 | // Represents the definition of an analog pin, which should contain all the info required to open, close, read and write the pin 22 | // using FS drivers. 23 | type ODroidC1AnalogModulePinDef struct { 24 | pin Pin 25 | analogLogical int 26 | } 27 | 28 | // A map of GPIO pin definitions. 29 | type ODroidC1AnalogModulePinDefMap map[Pin]*ODroidC1AnalogModulePinDef 30 | 31 | type ODroidC1AnalogModuleOpenPin struct { 32 | pin Pin 33 | analogLogical int 34 | 35 | // path to file representing analog pin 36 | analogFile string 37 | 38 | valueFile *os.File 39 | } 40 | 41 | func NewODroidC1AnalogModule(name string) (result *ODroidC1AnalogModule) { 42 | result = &ODroidC1AnalogModule{name: name} 43 | result.openPins = make(map[Pin]*ODroidC1AnalogModuleOpenPin) 44 | return result 45 | } 46 | 47 | // Set options of the module. Parameters we look for include: 48 | // - "pins" - an object of type ODroidC1AnalogModulePinDefMap 49 | func (module *ODroidC1AnalogModule) SetOptions(options map[string]interface{}) error { 50 | v := options["pins"] 51 | if v == nil { 52 | return fmt.Errorf("Module '%s' SetOptions() did not get 'pins' values", module.GetName()) 53 | } 54 | 55 | module.definedPins = v.(ODroidC1AnalogModulePinDefMap) 56 | return nil 57 | } 58 | 59 | // enable GPIO module. It doesn't allocate any pins immediately. 60 | func (module *ODroidC1AnalogModule) Enable() error { 61 | // once-off initialisation of analog 62 | if !module.analogInitialised { 63 | module.analogInitialised = true 64 | 65 | // attempt to assign all pins to this module 66 | for pin, _ := range module.definedPins { 67 | // attempt to assign this pin for this module. 68 | e := AssignPin(pin, module) 69 | if e != nil { 70 | return e 71 | } 72 | e = module.makeOpenAnalogPin(pin) 73 | if e != nil { 74 | return e 75 | } 76 | } 77 | } 78 | return nil 79 | } 80 | 81 | // disables module and release any pins assigned. 82 | func (module *ODroidC1AnalogModule) Disable() error { 83 | // Unassign any pins we may have assigned 84 | for pin, _ := range module.definedPins { 85 | // attempt to assign this pin for this module. 86 | UnassignPin(pin) 87 | } 88 | 89 | // if there are any open analog pins, close them 90 | for _, openPin := range module.openPins { 91 | openPin.analogClose() 92 | } 93 | return nil 94 | } 95 | 96 | func (module *ODroidC1AnalogModule) GetName() string { 97 | return module.name 98 | } 99 | 100 | func (module *ODroidC1AnalogModule) AnalogRead(pin Pin) (value int, e error) { 101 | openPin := module.openPins[pin] 102 | if openPin == nil { 103 | return 0, errors.New("Pin is being read for analog value but has not been opened. Have you called PinMode?") 104 | } 105 | return openPin.analogGetValue() 106 | } 107 | 108 | func (module *ODroidC1AnalogModule) makeOpenAnalogPin(pin Pin) error { 109 | p := module.definedPins[pin] 110 | if p == nil { 111 | return fmt.Errorf("Pin %d is not known to analog module", pin) 112 | } 113 | 114 | path := fmt.Sprintf("/sys/class/saradc/saradc_ch%d", p.analogLogical) 115 | result := &ODroidC1AnalogModuleOpenPin{pin: pin, analogLogical: p.analogLogical, analogFile: path} 116 | 117 | module.openPins[pin] = result 118 | 119 | e := result.analogOpen() 120 | if e != nil { 121 | return e 122 | } 123 | 124 | return nil 125 | } 126 | 127 | func (op *ODroidC1AnalogModuleOpenPin) analogOpen() error { 128 | // Open analog input file computed from the calculated path of actual analog files and the analog pin name 129 | f, e := os.OpenFile(op.analogFile, os.O_RDONLY, 0666) 130 | op.valueFile = f 131 | 132 | return e 133 | } 134 | 135 | func (op *ODroidC1AnalogModuleOpenPin) analogGetValue() (int, error) { 136 | var b []byte 137 | b = make([]byte, 5) 138 | n, e := op.valueFile.ReadAt(b, 0) 139 | 140 | // if there's an error and no byte were read, quit now. If we didn't get all the bytes we asked for, which 141 | // is generally the case, we will get an error as well but would have got some bytes. 142 | if e != nil && n == 0 { 143 | return 0, e 144 | } 145 | 146 | value, e := strconv.Atoi(string(b[:n-1])) 147 | 148 | return value, e 149 | } 150 | 151 | func (op *ODroidC1AnalogModuleOpenPin) analogClose() error { 152 | return op.valueFile.Close() 153 | } 154 | -------------------------------------------------------------------------------- /module_preassigned.go: -------------------------------------------------------------------------------- 1 | package hwio 2 | 3 | import "fmt" 4 | 5 | // This is a dummy module for devices that have pins that are pre-assigned, but not to any of the supported 6 | // modules on the device. It is passed a list of these pre-assigned pins. e.g. on BeagleBone Black, it covers 7 | // pins that are defined for HDMI, MMC and mcasp0. On the default configuration, these pins are pre-assigned 8 | // with device tree configuration, so they cannot be assigned for gpio (without custom device tree) 9 | type PreassignedModule struct { 10 | name string 11 | pins PinList 12 | } 13 | 14 | func NewPreassignedModule(name string) (result *PreassignedModule) { 15 | result = &PreassignedModule{name: name} 16 | return result 17 | } 18 | 19 | func (module *PreassignedModule) SetOptions(options map[string]interface{}) error { 20 | // get the pins 21 | vp := options["pins"] 22 | if vp == nil { 23 | return fmt.Errorf("Module '%s' SetOptions() did not get 'pins' values", module.GetName()) 24 | } 25 | module.pins = vp.(PinList) 26 | 27 | return nil 28 | } 29 | 30 | func (module *PreassignedModule) Enable() error { 31 | return AssignPins(module.pins, module) 32 | } 33 | 34 | func (module *PreassignedModule) Disable() error { 35 | return UnassignPins(module.pins) 36 | } 37 | 38 | func (module *PreassignedModule) GetName() string { 39 | return module.name 40 | } 41 | -------------------------------------------------------------------------------- /pin.go: -------------------------------------------------------------------------------- 1 | package hwio 2 | 3 | import ( 4 | "strings" 5 | ) 6 | 7 | // Definitions relating to pins. 8 | type PinIOMode int 9 | 10 | // The modes for PinMode. 11 | const ( 12 | INPUT PinIOMode = iota 13 | OUTPUT 14 | INPUT_PULLUP 15 | INPUT_PULLDOWN 16 | ) 17 | 18 | // String representation of pin IO mode 19 | func (mode PinIOMode) String() string { 20 | switch mode { 21 | case INPUT: 22 | return "INPUT" 23 | case OUTPUT: 24 | return "OUTPUT" 25 | case INPUT_PULLUP: 26 | return "INPUT_PULLUP" 27 | case INPUT_PULLDOWN: 28 | return "INPUT_PULLDOWN" 29 | } 30 | return "" 31 | } 32 | 33 | // Convenience constants for digital pin values. 34 | const ( 35 | HIGH = 1 36 | LOW = 0 37 | ) 38 | 39 | type Pin int 40 | 41 | type PinDef struct { 42 | pin Pin // the pin, also in the map key of HardwarePinMap 43 | names []string // a list of names for the pin as defined by Driver. There should be at least one. The first is the canonical name. 44 | modules []string // a list of module names that can use this pin 45 | } 46 | 47 | type PinList []Pin 48 | 49 | type HardwarePinMap map[Pin]*PinDef 50 | 51 | // Add a pin to the map 52 | func (m HardwarePinMap) Add(pin Pin, names []string, modules []string) { 53 | m[pin] = &PinDef{pin, names, modules} 54 | } 55 | 56 | // Given a pin number, return it's PinDef, or nil if that pin is not defined in the map 57 | func (m HardwarePinMap) GetPin(pin Pin) *PinDef { 58 | return m[pin] 59 | } 60 | 61 | // Provide a string representation of a logic pin and the capabilties it 62 | // supports. 63 | func (pd *PinDef) String() string { 64 | s := pd.Names() + " modules:" + strings.Join(pd.modules, ",") 65 | return s 66 | } 67 | 68 | // From the hwPinRefs, construct a string by appending them together. Not brilliantly efficient, 69 | // but its most for diagnostics anyway. 70 | func (pd *PinDef) Names() string { 71 | return strings.Join(pd.names, ",") 72 | } 73 | 74 | // // Determine if a pin has a particular capability. 75 | // func (pd *PinDef) HasCapability(cap Capability) bool { 76 | // // fmt.Printf("HasCap: checking (%s) has capability %s", pd.String(), cap.String()) 77 | // for _, v := range pd.capabilities { 78 | // if v == cap { 79 | // return true 80 | // } 81 | // } 82 | // return false 83 | // } 84 | -------------------------------------------------------------------------------- /servo/README.md: -------------------------------------------------------------------------------- 1 | The hwio/servo package contains definitions for driving servo motors using PWM pins. To initialise a servo, you need to first get the PWM 2 | module that the servo is attached. Here is an example of usage: 3 | 4 | import ( 5 | "github.com/mrmorphic/hwio" 6 | "github.com/mrmorphic/hwio/servo" 7 | ) 8 | 9 | m, e := hwio.GetModule("pwm2") 10 | if e != nil { 11 | fmt.Printf("could not get pwm module: %s\n", e) 12 | return 13 | } 14 | 15 | pwm := m.(hwio.PWMModule) 16 | 17 | pwm.Enable() 18 | 19 | // create a servo with a named pin. The pin name is passed to GetPin. You can also pass a Pin directly. 20 | servo, e := servo.New(pwm, "P8.13") 21 | 22 | // Set the servo angle, between 0 and 180 degrees. 23 | servo.Write(45) 24 | 25 | // Set the duty cycle to a specific number of microseconds 26 | servo.WriteMicroseconds(1500) 27 | 28 | The default values should work for regular servo motors. It assumes servos have a 0-180 degree range, corresponding to 29 | 1000-2000 microsecond duty. If your servo has different duty ranges, you can change them: 30 | 31 | // Set duty range of the servo to an 800-2500 microsecond range. 32 | servo.SetRange(800, 2500) 33 | 34 | The PWM and Pin are public properties of the PWM pin, so you can manipulate that directly if required. 35 | 36 | Write() and WriteMicroseconds() methods are asynchronous; they set the duty cycle but return immediately before the servo has 37 | moved to that position. This may differ from Arduino implementations. -------------------------------------------------------------------------------- /servo/servo.go: -------------------------------------------------------------------------------- 1 | package servo 2 | 3 | import ( 4 | "github.com/mrmorphic/hwio" 5 | ) 6 | 7 | const ( 8 | // default servo period, in milliseconds 9 | DEFAULT_SERVO_PERIOD = 20 10 | 11 | // defaults for servo duty, in microseconds 12 | DEFAULT_DUTY_MIN = 1000 13 | DEFAULT_DUTY_MAX = 2000 14 | ) 15 | 16 | type Servo struct { 17 | PWM hwio.PWMModule 18 | Pin hwio.Pin 19 | minDuty int // min duty in microseconds 20 | maxDuty int // max duty in microseconds 21 | } 22 | 23 | // Create a new servo and initialise it. 24 | func New(pwm hwio.PWMModule, pin interface{}) (*Servo, error) { 25 | var p hwio.Pin 26 | var e error 27 | 28 | switch pt := pin.(type) { 29 | case hwio.Pin: 30 | p = pt 31 | case string: 32 | p, e = hwio.GetPin(pt) 33 | if e != nil { 34 | return nil, e 35 | } 36 | } 37 | 38 | result := &Servo{PWM: pwm, Pin: p} 39 | 40 | // enable the servo 41 | e = pwm.EnablePin(p, true) 42 | if e != nil { 43 | return nil, e 44 | } 45 | 46 | e = result.SetPeriod(DEFAULT_SERVO_PERIOD) 47 | if e != nil { 48 | return nil, e 49 | } 50 | 51 | result.SetRange(DEFAULT_DUTY_MIN, DEFAULT_DUTY_MAX) 52 | 53 | return result, nil 54 | } 55 | 56 | // helper function to set the period of each cycle. Servos generally want this to be fixed, typically at 20ms. 57 | // This just sets the underling PWM period, so if you need less than 1 ms you can set that directly on the PWM. 58 | func (servo *Servo) SetPeriod(milliseconds int) error { 59 | return servo.PWM.SetPeriod(servo.Pin, int64(milliseconds*1000000)) 60 | } 61 | 62 | // Set the servo to the specified angle, typically 0-180. This sets the duty cycle proportionally between min and max, 63 | // which are defaulted to 1000-2000 microseconds range. 64 | func (servo *Servo) Write(angle int) { 65 | servo.WriteMicroseconds(hwio.Map(angle, 0, 180, servo.minDuty, servo.maxDuty)) 66 | } 67 | 68 | // Like the Arduino Servo.writeMicroseconds function. This is really setting the PWM duty directly, so it is possible 69 | // to write values too small or too large for the servo to track. 70 | func (servo *Servo) WriteMicroseconds(ms int) { 71 | // just pass to the underlying PWM pin. 72 | servo.PWM.SetDuty(servo.Pin, int64(ms*1000)) 73 | } 74 | 75 | // Set the minimum and maximum number of microseconds for the servo. Write maps 0-180 to these values. 76 | func (servo *Servo) SetRange(min int, max int) { 77 | servo.minDuty = min 78 | servo.maxDuty = max 79 | } 80 | --------------------------------------------------------------------------------