├── .gitignore ├── .travis.yml ├── Gopkg.lock ├── Gopkg.toml ├── LICENSE ├── Makefile ├── README.md ├── adc └── adc.go ├── dac └── dac.go ├── gpio ├── acme │ └── g25 │ │ ├── README.md │ │ ├── gpio.go │ │ └── gpio_test.go ├── gpio.go ├── gpio_test.go ├── watch.go └── watch_test.go ├── i2c ├── max │ ├── README.md │ ├── max518x.go │ └── max518x_test.go ├── microchip │ ├── README.md │ ├── mcp4725.go │ └── mcp4725_test.go └── ti │ ├── README.md │ ├── ads11xx.go │ ├── ads11xx_test.go │ ├── dacx578.go │ └── dacx578_test.go ├── iotest └── spi.go └── spi └── microchip ├── README.md ├── mcp3x0x.go └── mcp3x0x_test.go /.gitignore: -------------------------------------------------------------------------------- 1 | .coverage.out 2 | vendor/ 3 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: go 2 | 3 | go: 4 | - 1.11 5 | 6 | install: 7 | - make install 8 | 9 | # Move the code to the namespace advancedclimatesystems instead of 10 | # AdvancedClimateSystems to prevent import problems. 11 | - mkdir -p $HOME/gopath/src/github.com/advancedclimatesystems/ 12 | - mv -v $HOME/gopath/src/github.com/AdvancedClimateSystems/io $HOME/gopath/src/github.com/advancedclimatesystems/ 13 | - cd $HOME/gopath/src/github.com/advancedclimatesystems/io 14 | 15 | # For some reason our package is installed in it's own vendor/ folder. 16 | # The tests aren't ran against the fetched source code, but against the 17 | # code in vendor/. Remove this folder to prevent it from happening. 18 | - rm -r vendor/github.com/advancedclimatesystems/io 19 | 20 | script: make lint && make test 21 | -------------------------------------------------------------------------------- /Gopkg.lock: -------------------------------------------------------------------------------- 1 | # This file is autogenerated, do not edit; changes may be undone by the next 'dep ensure'. 2 | 3 | 4 | [[projects]] 5 | name = "github.com/davecgh/go-spew" 6 | packages = ["spew"] 7 | revision = "346938d642f2ec3594ed81d874461961cd0faa76" 8 | version = "v1.1.0" 9 | 10 | [[projects]] 11 | name = "github.com/pmezard/go-difflib" 12 | packages = ["difflib"] 13 | revision = "792786c7400a136282c1664665ae0a8db921c6c2" 14 | version = "v1.0.0" 15 | 16 | [[projects]] 17 | name = "github.com/stretchr/testify" 18 | packages = ["assert"] 19 | revision = "69483b4bd14f5845b5a1e55bca19e954e827f1d0" 20 | version = "v1.1.4" 21 | 22 | [[projects]] 23 | branch = "master" 24 | name = "golang.org/x/exp" 25 | packages = ["io/i2c","io/i2c/driver","io/spi","io/spi/driver"] 26 | revision = "be79676510e5ce293c77bb087cdab4796a461bdb" 27 | 28 | [solve-meta] 29 | analyzer-name = "dep" 30 | analyzer-version = 1 31 | inputs-digest = "9b88715842d1817907faf15f7ae8982249381aec4689eb51d16def3e35fa8be5" 32 | solver-name = "gps-cdcl" 33 | solver-version = 1 34 | -------------------------------------------------------------------------------- /Gopkg.toml: -------------------------------------------------------------------------------- 1 | 2 | # Gopkg.toml example 3 | # 4 | # Refer to https://github.com/golang/dep/blob/master/docs/Gopkg.toml.md 5 | # for detailed Gopkg.toml documentation. 6 | # 7 | # required = ["github.com/user/thing/cmd/thing"] 8 | # ignored = ["github.com/user/project/pkgX", "bitbucket.org/user/project/pkgA/pkgY"] 9 | # 10 | # [[constraint]] 11 | # name = "github.com/user/project" 12 | # version = "1.0.0" 13 | # 14 | # [[constraint]] 15 | # name = "github.com/user/project2" 16 | # branch = "dev" 17 | # source = "github.com/myfork/project2" 18 | # 19 | # [[override]] 20 | # name = "github.com/x/y" 21 | # version = "2.4.0" 22 | 23 | 24 | [[constraint]] 25 | name = "github.com/stretchr/testify" 26 | version = "1.1.4" 27 | 28 | [[constraint]] 29 | branch = "master" 30 | name = "golang.org/x/exp" 31 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Mozilla Public License, version 2.0 2 | 3 | 1. Definitions 4 | 5 | 1.1. "Contributor" 6 | 7 | means each individual or legal entity that creates, contributes to the 8 | creation of, or owns Covered Software. 9 | 10 | 1.2. "Contributor Version" 11 | 12 | means the combination of the Contributions of others (if any) used by a 13 | Contributor and that particular Contributor's Contribution. 14 | 15 | 1.3. "Contribution" 16 | 17 | means Covered Software of a particular Contributor. 18 | 19 | 1.4. "Covered Software" 20 | 21 | means Source Code Form to which the initial Contributor has attached the 22 | notice in Exhibit A, the Executable Form of such Source Code Form, and 23 | Modifications of such Source Code Form, in each case including portions 24 | thereof. 25 | 26 | 1.5. "Incompatible With Secondary Licenses" 27 | means 28 | 29 | a. that the initial Contributor has attached the notice described in 30 | Exhibit B to the Covered Software; or 31 | 32 | b. that the Covered Software was made available under the terms of 33 | version 1.1 or earlier of the License, but not also under the terms of 34 | a Secondary License. 35 | 36 | 1.6. "Executable Form" 37 | 38 | means any form of the work other than Source Code Form. 39 | 40 | 1.7. "Larger Work" 41 | 42 | means a work that combines Covered Software with other material, in a 43 | separate file or files, that is not Covered Software. 44 | 45 | 1.8. "License" 46 | 47 | means this document. 48 | 49 | 1.9. "Licensable" 50 | 51 | means having the right to grant, to the maximum extent possible, whether 52 | at the time of the initial grant or subsequently, any and all of the 53 | rights conveyed by this License. 54 | 55 | 1.10. "Modifications" 56 | 57 | means any of the following: 58 | 59 | a. any file in Source Code Form that results from an addition to, 60 | deletion from, or modification of the contents of Covered Software; or 61 | 62 | b. any new file in Source Code Form that contains any Covered Software. 63 | 64 | 1.11. "Patent Claims" of a Contributor 65 | 66 | means any patent claim(s), including without limitation, method, 67 | process, and apparatus claims, in any patent Licensable by such 68 | Contributor that would be infringed, but for the grant of the License, 69 | by the making, using, selling, offering for sale, having made, import, 70 | or transfer of either its Contributions or its Contributor Version. 71 | 72 | 1.12. "Secondary License" 73 | 74 | means either the GNU General Public License, Version 2.0, the GNU Lesser 75 | General Public License, Version 2.1, the GNU Affero General Public 76 | License, Version 3.0, or any later versions of those licenses. 77 | 78 | 1.13. "Source Code Form" 79 | 80 | means the form of the work preferred for making modifications. 81 | 82 | 1.14. "You" (or "Your") 83 | 84 | means an individual or a legal entity exercising rights under this 85 | License. For legal entities, "You" includes any entity that controls, is 86 | controlled by, or is under common control with You. For purposes of this 87 | definition, "control" means (a) the power, direct or indirect, to cause 88 | the direction or management of such entity, whether by contract or 89 | otherwise, or (b) ownership of more than fifty percent (50%) of the 90 | outstanding shares or beneficial ownership of such entity. 91 | 92 | 93 | 2. License Grants and Conditions 94 | 95 | 2.1. Grants 96 | 97 | Each Contributor hereby grants You a world-wide, royalty-free, 98 | non-exclusive license: 99 | 100 | a. under intellectual property rights (other than patent or trademark) 101 | Licensable by such Contributor to use, reproduce, make available, 102 | modify, display, perform, distribute, and otherwise exploit its 103 | Contributions, either on an unmodified basis, with Modifications, or 104 | as part of a Larger Work; and 105 | 106 | b. under Patent Claims of such Contributor to make, use, sell, offer for 107 | sale, have made, import, and otherwise transfer either its 108 | Contributions or its Contributor Version. 109 | 110 | 2.2. Effective Date 111 | 112 | The licenses granted in Section 2.1 with respect to any Contribution 113 | become effective for each Contribution on the date the Contributor first 114 | distributes such Contribution. 115 | 116 | 2.3. Limitations on Grant Scope 117 | 118 | The licenses granted in this Section 2 are the only rights granted under 119 | this License. No additional rights or licenses will be implied from the 120 | distribution or licensing of Covered Software under this License. 121 | Notwithstanding Section 2.1(b) above, no patent license is granted by a 122 | Contributor: 123 | 124 | a. for any code that a Contributor has removed from Covered Software; or 125 | 126 | b. for infringements caused by: (i) Your and any other third party's 127 | modifications of Covered Software, or (ii) the combination of its 128 | Contributions with other software (except as part of its Contributor 129 | Version); or 130 | 131 | c. under Patent Claims infringed by Covered Software in the absence of 132 | its Contributions. 133 | 134 | This License does not grant any rights in the trademarks, service marks, 135 | or logos of any Contributor (except as may be necessary to comply with 136 | the notice requirements in Section 3.4). 137 | 138 | 2.4. Subsequent Licenses 139 | 140 | No Contributor makes additional grants as a result of Your choice to 141 | distribute the Covered Software under a subsequent version of this 142 | License (see Section 10.2) or under the terms of a Secondary License (if 143 | permitted under the terms of Section 3.3). 144 | 145 | 2.5. Representation 146 | 147 | Each Contributor represents that the Contributor believes its 148 | Contributions are its original creation(s) or it has sufficient rights to 149 | grant the rights to its Contributions conveyed by this License. 150 | 151 | 2.6. Fair Use 152 | 153 | This License is not intended to limit any rights You have under 154 | applicable copyright doctrines of fair use, fair dealing, or other 155 | equivalents. 156 | 157 | 2.7. Conditions 158 | 159 | Sections 3.1, 3.2, 3.3, and 3.4 are conditions of the licenses granted in 160 | Section 2.1. 161 | 162 | 163 | 3. Responsibilities 164 | 165 | 3.1. Distribution of Source Form 166 | 167 | All distribution of Covered Software in Source Code Form, including any 168 | Modifications that You create or to which You contribute, must be under 169 | the terms of this License. You must inform recipients that the Source 170 | Code Form of the Covered Software is governed by the terms of this 171 | License, and how they can obtain a copy of this License. You may not 172 | attempt to alter or restrict the recipients' rights in the Source Code 173 | Form. 174 | 175 | 3.2. Distribution of Executable Form 176 | 177 | If You distribute Covered Software in Executable Form then: 178 | 179 | a. such Covered Software must also be made available in Source Code Form, 180 | as described in Section 3.1, and You must inform recipients of the 181 | Executable Form how they can obtain a copy of such Source Code Form by 182 | reasonable means in a timely manner, at a charge no more than the cost 183 | of distribution to the recipient; and 184 | 185 | b. You may distribute such Executable Form under the terms of this 186 | License, or sublicense it under different terms, provided that the 187 | license for the Executable Form does not attempt to limit or alter the 188 | recipients' rights in the Source Code Form under this License. 189 | 190 | 3.3. Distribution of a Larger Work 191 | 192 | You may create and distribute a Larger Work under terms of Your choice, 193 | provided that You also comply with the requirements of this License for 194 | the Covered Software. If the Larger Work is a combination of Covered 195 | Software with a work governed by one or more Secondary Licenses, and the 196 | Covered Software is not Incompatible With Secondary Licenses, this 197 | License permits You to additionally distribute such Covered Software 198 | under the terms of such Secondary License(s), so that the recipient of 199 | the Larger Work may, at their option, further distribute the Covered 200 | Software under the terms of either this License or such Secondary 201 | License(s). 202 | 203 | 3.4. Notices 204 | 205 | You may not remove or alter the substance of any license notices 206 | (including copyright notices, patent notices, disclaimers of warranty, or 207 | limitations of liability) contained within the Source Code Form of the 208 | Covered Software, except that You may alter any license notices to the 209 | extent required to remedy known factual inaccuracies. 210 | 211 | 3.5. Application of Additional Terms 212 | 213 | You may choose to offer, and to charge a fee for, warranty, support, 214 | indemnity or liability obligations to one or more recipients of Covered 215 | Software. However, You may do so only on Your own behalf, and not on 216 | behalf of any Contributor. You must make it absolutely clear that any 217 | such warranty, support, indemnity, or liability obligation is offered by 218 | You alone, and You hereby agree to indemnify every Contributor for any 219 | liability incurred by such Contributor as a result of warranty, support, 220 | indemnity or liability terms You offer. You may include additional 221 | disclaimers of warranty and limitations of liability specific to any 222 | jurisdiction. 223 | 224 | 4. Inability to Comply Due to Statute or Regulation 225 | 226 | If it is impossible for You to comply with any of the terms of this License 227 | with respect to some or all of the Covered Software due to statute, 228 | judicial order, or regulation then You must: (a) comply with the terms of 229 | this License to the maximum extent possible; and (b) describe the 230 | limitations and the code they affect. Such description must be placed in a 231 | text file included with all distributions of the Covered Software under 232 | this License. Except to the extent prohibited by statute or regulation, 233 | such description must be sufficiently detailed for a recipient of ordinary 234 | skill to be able to understand it. 235 | 236 | 5. Termination 237 | 238 | 5.1. The rights granted under this License will terminate automatically if You 239 | fail to comply with any of its terms. However, if You become compliant, 240 | then the rights granted under this License from a particular Contributor 241 | are reinstated (a) provisionally, unless and until such Contributor 242 | explicitly and finally terminates Your grants, and (b) on an ongoing 243 | basis, if such Contributor fails to notify You of the non-compliance by 244 | some reasonable means prior to 60 days after You have come back into 245 | compliance. Moreover, Your grants from a particular Contributor are 246 | reinstated on an ongoing basis if such Contributor notifies You of the 247 | non-compliance by some reasonable means, this is the first time You have 248 | received notice of non-compliance with this License from such 249 | Contributor, and You become compliant prior to 30 days after Your receipt 250 | of the notice. 251 | 252 | 5.2. If You initiate litigation against any entity by asserting a patent 253 | infringement claim (excluding declaratory judgment actions, 254 | counter-claims, and cross-claims) alleging that a Contributor Version 255 | directly or indirectly infringes any patent, then the rights granted to 256 | You by any and all Contributors for the Covered Software under Section 257 | 2.1 of this License shall terminate. 258 | 259 | 5.3. In the event of termination under Sections 5.1 or 5.2 above, all end user 260 | license agreements (excluding distributors and resellers) which have been 261 | validly granted by You or Your distributors under this License prior to 262 | termination shall survive termination. 263 | 264 | 6. Disclaimer of Warranty 265 | 266 | Covered Software is provided under this License on an "as is" basis, 267 | without warranty of any kind, either expressed, implied, or statutory, 268 | including, without limitation, warranties that the Covered Software is free 269 | of defects, merchantable, fit for a particular purpose or non-infringing. 270 | The entire risk as to the quality and performance of the Covered Software 271 | is with You. Should any Covered Software prove defective in any respect, 272 | You (not any Contributor) assume the cost of any necessary servicing, 273 | repair, or correction. This disclaimer of warranty constitutes an essential 274 | part of this License. No use of any Covered Software is authorized under 275 | this License except under this disclaimer. 276 | 277 | 7. Limitation of Liability 278 | 279 | Under no circumstances and under no legal theory, whether tort (including 280 | negligence), contract, or otherwise, shall any Contributor, or anyone who 281 | distributes Covered Software as permitted above, be liable to You for any 282 | direct, indirect, special, incidental, or consequential damages of any 283 | character including, without limitation, damages for lost profits, loss of 284 | goodwill, work stoppage, computer failure or malfunction, or any and all 285 | other commercial damages or losses, even if such party shall have been 286 | informed of the possibility of such damages. This limitation of liability 287 | shall not apply to liability for death or personal injury resulting from 288 | such party's negligence to the extent applicable law prohibits such 289 | limitation. Some jurisdictions do not allow the exclusion or limitation of 290 | incidental or consequential damages, so this exclusion and limitation may 291 | not apply to You. 292 | 293 | 8. Litigation 294 | 295 | Any litigation relating to this License may be brought only in the courts 296 | of a jurisdiction where the defendant maintains its principal place of 297 | business and such litigation shall be governed by laws of that 298 | jurisdiction, without reference to its conflict-of-law provisions. Nothing 299 | in this Section shall prevent a party's ability to bring cross-claims or 300 | counter-claims. 301 | 302 | 9. Miscellaneous 303 | 304 | This License represents the complete agreement concerning the subject 305 | matter hereof. If any provision of this License is held to be 306 | unenforceable, such provision shall be reformed only to the extent 307 | necessary to make it enforceable. Any law or regulation which provides that 308 | the language of a contract shall be construed against the drafter shall not 309 | be used to construe this License against a Contributor. 310 | 311 | 312 | 10. Versions of the License 313 | 314 | 10.1. New Versions 315 | 316 | Mozilla Foundation is the license steward. Except as provided in Section 317 | 10.3, no one other than the license steward has the right to modify or 318 | publish new versions of this License. Each version will be given a 319 | distinguishing version number. 320 | 321 | 10.2. Effect of New Versions 322 | 323 | You may distribute the Covered Software under the terms of the version 324 | of the License under which You originally received the Covered Software, 325 | or under the terms of any subsequent version published by the license 326 | steward. 327 | 328 | 10.3. Modified Versions 329 | 330 | If you create software not governed by this License, and you want to 331 | create a new license for such software, you may create and use a 332 | modified version of this License if you rename the license and remove 333 | any references to the name of the license steward (except to note that 334 | such modified license differs from this License). 335 | 336 | 10.4. Distributing Source Code Form that is Incompatible With Secondary 337 | Licenses If You choose to distribute Source Code Form that is 338 | Incompatible With Secondary Licenses under the terms of this version of 339 | the License, the notice described in Exhibit B of this License must be 340 | attached. 341 | 342 | Exhibit A - Source Code Form License Notice 343 | 344 | This Source Code Form is subject to the 345 | terms of the Mozilla Public License, v. 346 | 2.0. If a copy of the MPL was not 347 | distributed with this file, You can 348 | obtain one at 349 | http://mozilla.org/MPL/2.0/. 350 | 351 | If it is not possible or desirable to put the notice in a particular file, 352 | then You may include the notice in a location (such as a LICENSE file in a 353 | relevant directory) where a recipient would be likely to look for such a 354 | notice. 355 | 356 | You may add additional accurate notices of copyright ownership. 357 | 358 | Exhibit B - "Incompatible With Secondary Licenses" Notice 359 | 360 | This Source Code Form is "Incompatible 361 | With Secondary Licenses", as defined by 362 | the Mozilla Public License, v. 2.0. 363 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | PACKAGES=$(shell go list ./... | grep -v /vendor) 2 | 3 | help: ## Print help text. 4 | @grep -E '^[a-zA-Z_-]+:.*?## .*$$' $(MAKEFILE_LIST) | sort | awk 'BEGIN {FS = ":.*?## "}; {printf "\033[36m%-30s\033[0m %s\n", $$1, $$2}' 5 | 6 | install: # Install depencies. 7 | @go get -u github.com/golang/dep/cmd/dep 8 | @dep ensure 9 | @go get -u github.com/golang/lint/golint 10 | @go get -u github.com/kisielk/errcheck 11 | @go get -u github.com/client9/misspell/cmd/misspell 12 | 13 | lint: ## Check code using various linters and static checkers. 14 | @echo "Running gofmt..." 15 | @gofmt -d $(shell find . -type f -name '*.go' -not -path "./vendor/*") 16 | 17 | @echo "Running go vet..." 18 | @for package in $(PACKAGES); do \ 19 | go vet -v $$package || exit 1; \ 20 | done 21 | 22 | @echo "Running golint..." 23 | @for package in $(PACKAGES); do \ 24 | golint -set_exit_status $$package || exit 1; \ 25 | done 26 | 27 | @echo "Running errcheck..." 28 | @for package in $(PACKAGES); do \ 29 | errcheck -ignore 'Close' -ignoretests $$package || exit 1; \ 30 | done 31 | 32 | @echo "Running misspell..." 33 | @for package in $(PACKAGES); do \ 34 | misspell -error $$package; \ 35 | done 36 | 37 | test: ## Run unit tests and print test coverage. 38 | @for package in $(PACKAGES); do \ 39 | touch .coverage.out; \ 40 | go test -race -coverprofile .coverage.out $$package && go tool cover -func=.coverage.out || exit 1; \ 41 | rm .coverage.out; \ 42 | done 43 | 44 | 45 | .PHONY: help install lint test 46 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | [![Build Status](https://travis-ci.org/AdvancedClimateSystems/io.svg?branch=master)](https://travis-ci.org/AdvancedClimateSystems/io) 2 | 3 | # IO 4 | 5 | Go packages for pheripheral I/O. It contains driver for the following IC's: 6 | 7 | * SPI 8 | * [Microchip][spi/microchip] 9 | * MCP3004 10 | * MCP3008 11 | * MCP3204 12 | * MCP3208 13 | * I2C 14 | * [Maximum Integrated][i2c/max] 15 | * MAX5813 16 | * MAX5814 17 | * MAX5815 18 | * [Microchip][i2c/microchip] 19 | * MCP4725 20 | * [Texas Instruments][i2c/ti] 21 | * ADS1100 22 | * ADS1110 23 | * DAC5578 24 | * DAC6578 25 | * DAC7578 26 | * GPIO 27 | * [Acme Systems][gpio/acme] 28 | * Aria G25 29 | 30 | ## License 31 | 32 | IO is licensed under [Mozilla Public License][mpl] © 2017 [Advanced Climate 33 | System][acs]. 34 | 35 | [acs]: http://advancedclimate.nl 36 | [mpl]: LICENSE 37 | [i2c/max]: https://godoc.org/github.com/AdvancedClimateSystems/io/i2c/max 38 | [i2c/microchip]: https://godoc.org/github.com/AdvancedClimateSystems/io/i2c/microchip 39 | [i2c/ti]: https://godoc.org/github.com/AdvancedClimateSystems/io/i2c/ti 40 | [spi/microchip]: https://godoc.org/github.com/AdvancedClimateSystems/io/spi/microchip 41 | [gpio/acme]: https://godoc.org/github.com/AdvancedClimateSystems/io/gpio/acme 42 | -------------------------------------------------------------------------------- /adc/adc.go: -------------------------------------------------------------------------------- 1 | // Package adc defines the ADC interface for Analog Digital Converters. 2 | package adc 3 | 4 | // InputType defines how an ADC samples the input signal. A single-ended input 5 | // samples its input in the range from the ground (0V) to Vref, that is the 6 | // reference input. A 10-bits ADC with a reference input of 5V has a precision 7 | // of (5 - 0) / 1024 = 0.0049V = 4.9mV on single-ended inputs. 8 | // 9 | // A (pseudo-)differential output input samples its input between voltage of a 10 | // second pin and Vref, allowing measurements with higher precision. Assume a 11 | // voltage on that second pin is 3V and Vref is 5V. That gives a precision of 12 | // (5 - 3) / 1024 = 0.0020V = 2.0mV for measurements between 3V and 5V. Of 13 | // course, values between 0V and 3V cannot be measured in this case. 14 | type InputType int 15 | 16 | const ( 17 | // SingleEnded configures the inputs of an ADC as single-ended. 18 | SingleEnded InputType = 0 19 | 20 | // PseudoDifferential configures the inputs of an ADC as pseudo-differential. 21 | PseudoDifferential InputType = 1 22 | ) 23 | 24 | // ADC is the interface that wraps an OutputCode and Voltage method. The first 25 | // returns the digital output code of a channel. The latter returns the voltage 26 | // of a channel. 27 | type ADC interface { 28 | // OutputCode queries the channel and returns its digital output code. 29 | // This should be the raw value. If exists, PGA should not be applied. 30 | OutputCode(channel int) (int, error) 31 | // Voltage queries the channel of an ADC and returns its voltage. 32 | Voltage(channel int) (float64, error) 33 | } 34 | -------------------------------------------------------------------------------- /dac/dac.go: -------------------------------------------------------------------------------- 1 | // Package dac defines the DAC 2 | package dac 3 | 4 | // DAC is the interface to set the output voltage(s) of a Digital Analog 5 | // Converter. 6 | type DAC interface { 7 | // SetVoltage sets output voltage of a channel. 8 | SetVoltage(voltage float64, channel int) error 9 | 10 | // SetInputCode sets output voltage using an number that is between 11 | // and including 0 - (max resolution of DAC - 1). 12 | SetInputCode(code, channel int) error 13 | } 14 | -------------------------------------------------------------------------------- /gpio/acme/g25/README.md: -------------------------------------------------------------------------------- 1 | [![godoc](https://img.shields.io/badge/godoc-reference-blue.svg?style=flat)](https://godoc.org/github.com/AdvancedClimateSystems/io/acme/g25) 2 | 3 | # Aria G25 4 | 5 | Package g25 implements drivers for the GPIO of the [Aria G25](https://www.acmesystems.it/aria) produced by [Acme Systems](https://www.acmesystems.it/). 6 | 7 | Sample usage: 8 | 9 | 10 | ```go 11 | package main 12 | 13 | import ( 14 | "log" 15 | "time" 16 | 17 | "github.com/advancedclimatesystems/io/gpio/acme/g25" 18 | "github.com/advancedclimatesystems/io/gpio" 19 | ) 20 | 21 | func main() { 22 | outPin, _ := g25.NewPin("N16") 23 | _ = outPin.SetDirection(gpio.OutDirection) 24 | 25 | inPin, _ := g25.NewPin("N20") 26 | _ = inPin.SetDirection(gpio.InDirection) 27 | _ = inPin.SetEdge(gpio.RisingEdge, func(p *gpio.Pin) { 28 | log.Printf("wow") 29 | }) 30 | 31 | for i := 0; i < 4; i++ { 32 | _ = outPin.SetHigh() 33 | time.Sleep(1000 * time.Millisecond) 34 | _ = outPin.SetLow() 35 | time.Sleep(1000 * time.Millisecond) 36 | } 37 | } 38 | ``` 39 | -------------------------------------------------------------------------------- /gpio/acme/g25/gpio.go: -------------------------------------------------------------------------------- 1 | // +build linux 2 | 3 | // Package g25 contains GPIO drivers for the Acme Systems Aria G25 4 | // 5 | // The Aria G25 contains up to 60 GPIO pins. This package implements all GPIO 6 | // operations such as getting/setting the value, setting the direction and 7 | // changing the active low. The package provides a mapping between the 8 | // pinnumber, the atmel ID and the kernel ID. 9 | // https://www.acmesystems.it/aria 10 | package g25 11 | 12 | import ( 13 | "fmt" 14 | "io/ioutil" 15 | "strconv" 16 | 17 | "github.com/advancedclimatesystems/io/gpio" 18 | ) 19 | 20 | var w gpio.Watcher 21 | 22 | // NewPin creates a new pin with a kernel ID based on the pin name found 23 | // here: https://www.acmesystems.it/aria. It assumes the kernel has version 3.1x 24 | // if this is not the case, use the NewPinV26 instead. 25 | func NewPin(id string) (gpio.GPIO, error) { 26 | k, err := getKernelVersion() 27 | if err != nil { 28 | return nil, err 29 | } 30 | 31 | kernelID, err := getkernelID(k, id) 32 | if err != nil { 33 | return nil, err 34 | } 35 | 36 | if err := setupWatcher(); err != nil { 37 | return nil, err 38 | } 39 | 40 | // The file created by export is always called pio, followed by ic pin ID, 41 | // but without the fist character. So exporting N2 gives an file called pioC0. 42 | gpio := gpio.NewPin(kernelID, fmt.Sprintf("pio%v", g25Id[id].icPin[1:]), w) 43 | if err := gpio.Export(); err != nil { 44 | return nil, err 45 | } 46 | return gpio, nil 47 | } 48 | 49 | // setupWatcher creates a new watcher and starts it, if its not already running. 50 | func setupWatcher() error { 51 | // A Watcher only needs to be setup once, but an error can't be handled in an 52 | // init function. 53 | var err error 54 | if w == nil { 55 | w, err = gpio.NewWatcher() 56 | if err != nil { 57 | return err 58 | } 59 | go func() { 60 | err = w.Watch() 61 | defer w.Close() 62 | }() 63 | } 64 | return err 65 | } 66 | 67 | // getKernelID returns the corrent kernel ID, based on the kernel version and id. 68 | func getkernelID(k int, id string) (int, error) { 69 | pinID, ok := g25Id[id] 70 | if !ok { 71 | return 0, fmt.Errorf("id %v not known", id) 72 | } 73 | 74 | var kernelID int 75 | if k < 3 { 76 | kernelID = pinID.kernelID26 77 | } else { 78 | kernelID = pinID.kernelID31 79 | } 80 | return kernelID, nil 81 | } 82 | 83 | // getkernelVersion get s the kernel version from /proc/version 84 | func getKernelVersion() (int, error) { 85 | data, err := ioutil.ReadFile("/proc/version") 86 | if err != nil || len(data) < 15 { 87 | return 0, err 88 | } 89 | // The 14th byte should be the major version, which is what we are loooking for. 90 | return strconv.Atoi(string(data[14])) 91 | } 92 | 93 | // g25Id provides a mapping between the "pin name" and all other indentifiers 94 | // of a pin. 95 | var g25Id = map[string]struct { 96 | // Names such as N2, N3, E10, E6 97 | pinName string 98 | // Names such as PC0, PC1, PC30, PC26. These are used to figure out the 99 | // name of the file created by exporting the pin. 100 | icPin string 101 | // Kernel ID for version 2.6. 102 | kernelID26 int 103 | // Kernel ID for version 3.1. 104 | kernelID31 int 105 | }{ 106 | "N2": {"N2", "PC0", 96, 64}, 107 | "N3": {"N3", "PC1", 97, 65}, 108 | "N4": {"N4", "PC2", 98, 66}, 109 | "N5": {"N5", "PC3", 99, 67}, 110 | "N6": {"N6", "PC4", 100, 68}, 111 | "N7": {"N7", "PC5", 101, 69}, 112 | "N8": {"N8", "PC6", 102, 70}, 113 | "N9": {"N9", "PC7", 103, 71}, 114 | "N10": {"N10", "PC8", 104, 72}, 115 | "N11": {"N11", "PC9", 105, 73}, 116 | "N12": {"N12", "PC10", 106, 74}, 117 | "N13": {"N13", "PC11", 107, 75}, 118 | "N14": {"N14", "PC12", 108, 76}, 119 | "N15": {"N15", "PC13", 109, 77}, 120 | "N16": {"N16", "PC14", 110, 78}, 121 | "N17": {"N17", "PC15", 111, 79}, 122 | "N18": {"N18", "PC16", 112, 80}, 123 | "N19": {"N19", "PC17", 113, 81}, 124 | "N20": {"N20", "PC18", 114, 82}, 125 | "N21": {"N21", "PC19", 115, 83}, 126 | "N22": {"N22", "PC20", 116, 84}, 127 | "N23": {"N23", "PC21", 117, 85}, 128 | 129 | "E2": {"E2", "PC22", 118, 86}, 130 | "E3": {"E3", "PC23", 119, 87}, 131 | "E4": {"E4", "PC24", 120, 88}, 132 | "E5": {"E5", "PC25", 121, 89}, 133 | "E6": {"E6", "PC26", 122, 90}, 134 | "E7": {"E7", "PC27", 123, 91}, 135 | "E8": {"E8", "PC28", 124, 92}, 136 | "E9": {"E9", "PC29", 125, 93}, 137 | "E10": {"E10", "PC30", 126, 94}, 138 | "E11": {"E11", "PC31", 127, 95}, 139 | 140 | "S2": {"S2", "PA21", 53, 21}, 141 | "S3": {"S3", "PA20", 52, 20}, 142 | "S4": {"S4", "PA19", 51, 19}, 143 | "S5": {"S5", "PA18", 50, 18}, 144 | "S6": {"S6", "PA17", 49, 17}, 145 | "S7": {"S7", "PA16", 48, 16}, 146 | "S8": {"S8", "PA15", 47, 15}, 147 | "S9": {"S9", "PA14", 46, 14}, 148 | "S10": {"S10", "PA13", 45, 13}, 149 | "S11": {"S11", "PA12", 44, 12}, 150 | "S12": {"S12", "PA11", 43, 11}, 151 | "S15": {"S15", "PA8", 40, 8}, 152 | "S16": {"S16", "PA7", 39, 7}, 153 | "S17": {"S17", "PA6", 38, 6}, 154 | "S18": {"S18", "PA5", 37, 5}, 155 | "S19": {"S19", "PA4", 36, 4}, 156 | "S20": {"S20", "PA3", 35, 3}, 157 | "S21": {"S21", "PA2", 34, 2}, 158 | "S22": {"S22", "PA1", 33, 1}, 159 | "S23": {"S23", "PA0", 32, 0}, 160 | 161 | "W9": {"W9", "PA22", 54, 22}, 162 | "W10": {"W10", "PA23", 55, 23}, 163 | "W11": {"W11", "PA24", 56, 24}, 164 | "W12": {"W12", "PA25", 57, 25}, 165 | "W13": {"W13", "PA26", 58, 26}, 166 | "W14": {"W14", "PA27", 59, 27}, 167 | "W15": {"W15", "PA28", 60, 28}, 168 | "W16": {"W16", "PA29", 61, 29}, 169 | "W17": {"W17", "PA30", 62, 30}, 170 | "W18": {"W18", "PA31", 63, 31}, 171 | "W20": {"W20", "PB11", 75, 43}, 172 | "W21": {"W21", "PB12", 76, 44}, 173 | "W22": {"W22", "PB13", 77, 45}, 174 | "W23": {"W23", "PB14", 78, 46}, 175 | } 176 | -------------------------------------------------------------------------------- /gpio/acme/g25/gpio_test.go: -------------------------------------------------------------------------------- 1 | package g25 2 | 3 | import ( 4 | "errors" 5 | "log" 6 | "testing" 7 | "time" 8 | 9 | "github.com/advancedclimatesystems/io/gpio" 10 | "github.com/stretchr/testify/assert" 11 | ) 12 | 13 | func TestGetKernelID(t *testing.T) { 14 | tests := []struct { 15 | kv int 16 | id string 17 | err error 18 | kid int 19 | }{ 20 | {2, "N2", nil, 96}, 21 | {3, "N2", nil, 64}, 22 | {2, "E2", nil, 118}, 23 | {3, "E2", nil, 86}, 24 | 25 | {2, "N999", errors.New("id N999 not known"), 0}, 26 | {3, "N999", errors.New("id N999 not known"), 0}, 27 | {2, "W0", errors.New("id W0 not known"), 0}, 28 | {3, "W0", errors.New("id W0 not known"), 0}, 29 | 30 | {1, "N2", nil, 96}, 31 | {4, "N2", nil, 64}, 32 | } 33 | for _, test := range tests { 34 | kid, err := getkernelID(test.kv, test.id) 35 | assert.Equal(t, test.kid, kid) 36 | assert.Equal(t, test.err, err) 37 | } 38 | } 39 | 40 | func ExampleNewPin() { 41 | outPin, _ := NewPin("N16") 42 | _ = outPin.SetDirection(gpio.OutDirection) 43 | 44 | inPin, _ := NewPin("N20") 45 | _ = inPin.SetDirection(gpio.InDirection) 46 | _ = inPin.SetEdge(gpio.RisingEdge, func(p *gpio.Pin) { 47 | log.Printf("wow") 48 | }) 49 | 50 | for i := 0; i < 4; i++ { 51 | _ = outPin.SetHigh() 52 | time.Sleep(1000 * time.Millisecond) 53 | _ = outPin.SetLow() 54 | time.Sleep(1000 * time.Millisecond) 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /gpio/gpio.go: -------------------------------------------------------------------------------- 1 | // +build linux 2 | 3 | // Package gpio contains an interface and implementation for controlling GPIO 4 | // pins via the sysfs interface. 5 | // 6 | // This packages does not contain any vendor specific implementations of GPIO 7 | // pins, however the Pin struct in this package can be embedded in another 8 | // struct which implements vendor specific functionality. 9 | package gpio 10 | 11 | import ( 12 | "errors" 13 | "fmt" 14 | "os" 15 | "strconv" 16 | ) 17 | 18 | // basePath is where the GPIO pins can be found. 19 | const basePath = "/sys/class/gpio" 20 | 21 | // Edge describes on what edge a function should be called. 22 | type Edge string 23 | 24 | const ( 25 | // RisingEdge is triggered when the values goes from low to high. 26 | RisingEdge Edge = "rising" 27 | // FallingEdge is triggered when the values goes from high to low. 28 | FallingEdge Edge = "falling" 29 | // BothEdge is triggered when the values goes from high to low or from low to high. 30 | BothEdge Edge = "both" 31 | // NoneEdge never triggers. 32 | NoneEdge Edge = "none" 33 | ) 34 | 35 | // EdgeEvent is a type of function that can be used as a vcallback to watcher 36 | type EdgeEvent func(pin *Pin) 37 | 38 | // Direction is the direction of the dataflow. 39 | type Direction string 40 | 41 | const ( 42 | // InDirection means a value can be read from the pin. 43 | InDirection Direction = "in" 44 | // OutDirection means a value can written to the pin. 45 | OutDirection Direction = "out" 46 | ) 47 | 48 | // GPIO is an interface for GPIO pins. 49 | type GPIO interface { 50 | Value() (int, error) 51 | SetHigh() error 52 | SetLow() error 53 | 54 | Direction() (Direction, error) 55 | SetDirection(d Direction) error 56 | 57 | Edge() (Edge, error) 58 | SetEdge(edge Edge, f EdgeEvent) error 59 | 60 | ActiveLow() (bool, error) 61 | SetActiveLow(invert bool) error 62 | 63 | Export() error 64 | Unexport() error 65 | } 66 | 67 | // Pin is an implementation of the GPIO interface. It can be embedded in vendor 68 | // specific implentations. 69 | type Pin struct { 70 | KernelID int 71 | // The kernel ID is often needed as []byte to write to a file. 72 | kernelIDByte []byte 73 | pinBase string 74 | rwHelper rwHelper 75 | w Watcher 76 | } 77 | 78 | // NewPin creates an instance of Pin. 79 | // The kernelID is the ID used to expose the pin. The filename is the name of 80 | // the folder that contains files such as value and edge. This folder gets 81 | // created when the in is exported and is often named gpio. 82 | func NewPin(kernelID int, pinBase string, w Watcher) *Pin { 83 | return &Pin{ 84 | KernelID: kernelID, 85 | kernelIDByte: []byte(strconv.Itoa(kernelID)), 86 | pinBase: pinBase, 87 | rwHelper: new(baseReaderWriter), 88 | w: w, 89 | } 90 | } 91 | 92 | // Direction returns the curent direction of the pin. 93 | func (p *Pin) Direction() (Direction, error) { 94 | b := make([]byte, 3) 95 | n, err := p.read(b, "direction") 96 | if err != nil { 97 | return OutDirection, err 98 | } 99 | if n == 0 { 100 | return OutDirection, errors.New("not enough bytes to read") 101 | } 102 | if string(b[:n]) == "out" { 103 | return OutDirection, nil 104 | } 105 | if string(b[:n-1]) == "in" { 106 | return InDirection, nil 107 | } 108 | return OutDirection, fmt.Errorf("not a known direction: '%v'", string(b[:n])) 109 | } 110 | 111 | // SetDirection configures the pin as an input or output. 112 | func (p *Pin) SetDirection(d Direction) error { 113 | data := []byte(d) 114 | return p.write(data, "direction") 115 | } 116 | 117 | // Value returns the value of the pin. The pin must be in the 'in' direction. 118 | func (p *Pin) Value() (int, error) { 119 | b := make([]byte, 1) 120 | n, err := p.read(b, "value") 121 | if err != nil { 122 | return 0, err 123 | } 124 | if n != 1 { 125 | return 0, fmt.Errorf("expected 1 byte, got %v", n) 126 | } 127 | if string(b[:n]) == "1" { 128 | return 1, nil 129 | } 130 | if string(b[:n]) == "0" { 131 | return 0, nil 132 | } 133 | return 0, fmt.Errorf("not a known value: '%v'", string(b[:n])) 134 | } 135 | 136 | // SetLow writes a 0 to the Pin 137 | func (p *Pin) SetLow() error { 138 | data := []byte("0") 139 | return p.write(data, "value") 140 | } 141 | 142 | // SetHigh writes a 1 to the Pin. It also sets the pins direction to output. 143 | func (p *Pin) SetHigh() error { 144 | data := []byte("1") 145 | return p.write(data, "value") 146 | } 147 | 148 | // ActiveLow returns true if the the pin is inverted, i.e. it is true when 149 | // the value is low 150 | func (p *Pin) ActiveLow() (bool, error) { 151 | b := make([]byte, 1) 152 | n, err := p.read(b, "active_low") 153 | if err != nil { 154 | return false, err 155 | } 156 | if n != 1 { 157 | return false, fmt.Errorf("expected 1 byte, got %v", n) 158 | } 159 | if string(b[:n]) == "1" { 160 | return true, nil 161 | } 162 | if string(b[:n]) == "0" { 163 | return false, nil 164 | } 165 | return false, fmt.Errorf("not a known value: '%v'", string(b[:n])) 166 | } 167 | 168 | // SetActiveLow inverts the pins value, i.e. it is true when 169 | // the value is low. 170 | func (p *Pin) SetActiveLow(invert bool) error { 171 | var data []byte 172 | 173 | data = []byte("0") 174 | if invert { 175 | data = []byte("1") 176 | } 177 | 178 | return p.write(data, "active_low") 179 | } 180 | 181 | // Edge returns the current edge of the pin. 182 | func (p *Pin) Edge() (Edge, error) { 183 | b := make([]byte, 8) 184 | n, err := p.read(b, "edge") 185 | if err != nil { 186 | return NoneEdge, err 187 | } 188 | if n == 0 { 189 | return NoneEdge, errors.New("not enough bytes to read") 190 | } 191 | 192 | switch string(b[:n-1]) { 193 | case "rising": 194 | return RisingEdge, nil 195 | case "falling": 196 | return FallingEdge, nil 197 | case "both": 198 | return BothEdge, nil 199 | case "none": 200 | return NoneEdge, nil 201 | default: 202 | return NoneEdge, fmt.Errorf("not a known value: '%v'", string(b[:n])) 203 | } 204 | } 205 | 206 | // SetEdge sets an edge and sets up event handing for given edge. An edge can 207 | // only be set on a pin with the 'in' direction. 208 | func (p *Pin) SetEdge(e Edge, f EdgeEvent) error { 209 | b := []byte(e) 210 | valF, err := os.OpenFile(fmt.Sprintf("%v/%v/value", basePath, p.pinBase), os.O_RDWR, 0777) 211 | if err != nil { 212 | return err 213 | } 214 | // Wrap the callback function, so that the pin can be used as a parameter. 215 | callback := func() { 216 | f(p) 217 | } 218 | p.w.AddFile(valF) 219 | if err = p.w.AddEvent(int(valF.Fd()), callback); err != nil { 220 | return err 221 | } 222 | return p.write(b, "edge") 223 | } 224 | 225 | // Export exports the pin, if it wasn't exported already. 226 | func (p *Pin) Export() error { 227 | err := p.rwHelper.writeFromBase(p.kernelIDByte, "export") 228 | // The 'device or resource busy' error indicates the pin has already been 229 | // exported. Checking for specific error is a bit weird in Go. Maybe proper 230 | // error handling will come with Go 2.0 .... 231 | if fmt.Sprintf("%v", err) == fmt.Sprintf("write %v/export: device or resource busy", basePath) { 232 | return nil 233 | } 234 | return err 235 | } 236 | 237 | // Unexport unexports the pin. 238 | func (p *Pin) Unexport() error { 239 | return p.rwHelper.writeFromBase(p.kernelIDByte, "unexport") 240 | } 241 | 242 | func (p *Pin) read(b []byte, file string) (int, error) { 243 | return p.rwHelper.readFromBase(b, fmt.Sprintf("%v/%v", p.pinBase, file)) 244 | } 245 | 246 | func (p *Pin) write(b []byte, file string) error { 247 | return p.rwHelper.writeFromBase(b, fmt.Sprintf("%v/%v", p.pinBase, file)) 248 | } 249 | 250 | // rwHelper is a seperate interface for interacting with files. This makes it 251 | // possible to create mocks for reading and writing files, which is needed to 252 | // write proper tests. 253 | type rwHelper interface { 254 | readFromBase(b []byte, pathFromBase string) (int, error) 255 | writeFromBase(b []byte, pathFromBase string) error 256 | } 257 | 258 | // baseReaderWriter has methods to read/write gpio-related files. 259 | type baseReaderWriter struct{} 260 | 261 | // readFromBase reads data from a file into b. 262 | func (baseReaderWriter) readFromBase(b []byte, pathFromBase string) (int, error) { 263 | f, err := os.OpenFile(fmt.Sprintf("%v/%v", basePath, pathFromBase), os.O_RDONLY, 0777) 264 | if err != nil { 265 | return 0, err 266 | } 267 | defer f.Close() 268 | return f.Read(b) 269 | } 270 | 271 | // readFromBase writeFromBase writes data to a file. 272 | func (baseReaderWriter) writeFromBase(b []byte, pathFromBase string) error { 273 | f, err := os.OpenFile(fmt.Sprintf("%v/%v", basePath, pathFromBase), os.O_WRONLY, 0777) 274 | if err != nil { 275 | return err 276 | } 277 | defer f.Close() 278 | 279 | _, err = f.Write(b) 280 | return err 281 | } 282 | -------------------------------------------------------------------------------- /gpio/gpio_test.go: -------------------------------------------------------------------------------- 1 | package gpio 2 | 3 | import ( 4 | "errors" 5 | "fmt" 6 | "testing" 7 | 8 | "github.com/stretchr/testify/assert" 9 | ) 10 | 11 | type testValues struct { 12 | readVal []byte 13 | mockErr error 14 | prevPath string 15 | } 16 | 17 | type mockReaderWriter struct { 18 | // the testvalues pointer is needed to store/record the values. Storing 19 | // them into mockReaderWriter cant wortk, because the functions dont have 20 | // pointerrecievers, so the values would not actually be set as expected. 21 | v *testValues 22 | } 23 | 24 | // readFromBase reads data from a file into b. 25 | func (m mockReaderWriter) readFromBase(b []byte, pathFromBase string) (int, error) { 26 | if m.v.mockErr != nil { 27 | return 0, m.v.mockErr 28 | } 29 | 30 | n := 0 31 | for i, v := range m.v.readVal { 32 | if i < len(b) { 33 | b[i] = v 34 | n++ 35 | } 36 | } 37 | m.v.prevPath = pathFromBase 38 | return n, nil 39 | } 40 | 41 | // readFromBase writeFromBase writes data to a file. 42 | func (m mockReaderWriter) writeFromBase(b []byte, pathFromBase string) error { 43 | m.v.prevPath = pathFromBase 44 | m.v.readVal = b 45 | if m.v.mockErr != nil { 46 | return m.v.mockErr 47 | } 48 | return nil 49 | } 50 | 51 | func TestPinImplements(t *testing.T) { 52 | assert.Implements(t, (*GPIO)(nil), new(Pin)) 53 | } 54 | 55 | func TestNewPin(t *testing.T) { 56 | p := NewPin(1, "gpio1", new(watch)) 57 | assert.Equal(t, []byte("1"), p.kernelIDByte) 58 | assert.Equal(t, 1, p.KernelID) 59 | assert.Equal(t, p.pinBase, "gpio1") 60 | } 61 | 62 | func TestDirection(t *testing.T) { 63 | p := NewPin(1, "gpio1", new(watch)) 64 | 65 | tests := []struct { 66 | val string 67 | expected Direction 68 | err error 69 | }{ 70 | {"out", OutDirection, nil}, 71 | {"in\n", InDirection, nil}, 72 | {"not-a-valid-value", OutDirection, errors.New("not a known direction: 'not'")}, 73 | {"", OutDirection, errors.New("not enough bytes to read")}, 74 | } 75 | for _, test := range tests { 76 | mrw := mockReaderWriter{&testValues{readVal: []byte(test.val)}} 77 | p.rwHelper = mrw 78 | dir, err := p.Direction() 79 | assert.Equal(t, test.err, err) 80 | assert.Equal(t, test.expected, dir) 81 | assert.Equal(t, "gpio1/direction", mrw.v.prevPath) 82 | } 83 | 84 | p.rwHelper = mockReaderWriter{&testValues{mockErr: errors.New("error")}} 85 | dir, err := p.Direction() 86 | assert.Equal(t, "error", err.Error()) 87 | assert.Equal(t, OutDirection, dir) 88 | } 89 | 90 | func TestSetDirection(t *testing.T) { 91 | p := NewPin(1, "gpio1", new(watch)) 92 | mrw := mockReaderWriter{&testValues{}} 93 | p.rwHelper = mrw 94 | 95 | err := p.SetDirection(InDirection) 96 | assert.Nil(t, err) 97 | assert.Equal(t, "gpio1/direction", mrw.v.prevPath) 98 | } 99 | 100 | func TestValue(t *testing.T) { 101 | p := NewPin(1, "gpio1", new(watch)) 102 | 103 | tests := []struct { 104 | val string 105 | expected int 106 | err error 107 | }{ 108 | {"1", 1, nil}, 109 | {"0", 0, nil}, 110 | {"not-a-valid-value", 0, errors.New("not a known value: 'n'")}, 111 | {"", 0, errors.New("expected 1 byte, got 0")}, 112 | } 113 | for _, test := range tests { 114 | mrw := mockReaderWriter{&testValues{readVal: []byte(test.val)}} 115 | p.rwHelper = mrw 116 | val, err := p.Value() 117 | assert.Equal(t, test.err, err) 118 | assert.Equal(t, test.expected, val) 119 | assert.Equal(t, "gpio1/value", mrw.v.prevPath) 120 | } 121 | 122 | p.rwHelper = mockReaderWriter{&testValues{mockErr: errors.New("error")}} 123 | val, err := p.Value() 124 | assert.Equal(t, "error", err.Error()) 125 | assert.Equal(t, 0, val) 126 | } 127 | 128 | func TestSetHigh(t *testing.T) { 129 | p := NewPin(1, "gpio1", new(watch)) 130 | mrw := mockReaderWriter{&testValues{}} 131 | p.rwHelper = mrw 132 | 133 | err := p.SetHigh() 134 | assert.Nil(t, err) 135 | assert.Equal(t, "gpio1/value", mrw.v.prevPath) 136 | assert.Equal(t, []byte("1"), mrw.v.readVal) 137 | } 138 | 139 | func TestSetLow(t *testing.T) { 140 | mrw := mockReaderWriter{&testValues{}} 141 | p := &Pin{ 142 | KernelID: 1, 143 | kernelIDByte: []byte("1"), 144 | pinBase: "gpio1", 145 | rwHelper: mrw, 146 | } 147 | 148 | err := p.SetLow() 149 | assert.Nil(t, err) 150 | assert.Equal(t, "gpio1/value", mrw.v.prevPath) 151 | assert.Equal(t, []byte("0"), mrw.v.readVal) 152 | } 153 | 154 | func TestActiveLow(t *testing.T) { 155 | p := NewPin(1, "gpio1", new(watch)) 156 | 157 | tests := []struct { 158 | val string 159 | expected bool 160 | err error 161 | }{ 162 | {"1", true, nil}, 163 | {"0", false, nil}, 164 | {"not-a-valid-value", false, errors.New("not a known value: 'n'")}, 165 | {"", false, errors.New("expected 1 byte, got 0")}, 166 | } 167 | for _, test := range tests { 168 | mrw := mockReaderWriter{&testValues{readVal: []byte(test.val)}} 169 | p.rwHelper = mrw 170 | val, err := p.ActiveLow() 171 | assert.Equal(t, test.err, err) 172 | assert.Equal(t, test.expected, val) 173 | assert.Equal(t, "gpio1/active_low", mrw.v.prevPath) 174 | } 175 | 176 | p.rwHelper = mockReaderWriter{&testValues{mockErr: errors.New("error")}} 177 | val, err := p.ActiveLow() 178 | assert.Equal(t, "error", err.Error()) 179 | assert.Equal(t, false, val) 180 | } 181 | 182 | func TestSetActiveLow(t *testing.T) { 183 | p := NewPin(1, "gpio1", new(watch)) 184 | mrw := mockReaderWriter{&testValues{}} 185 | p.rwHelper = mrw 186 | 187 | err := p.SetActiveLow(true) 188 | assert.Nil(t, err) 189 | assert.Equal(t, "gpio1/active_low", mrw.v.prevPath) 190 | assert.Equal(t, []byte("1"), mrw.v.readVal) 191 | 192 | err = p.SetActiveLow(false) 193 | assert.Nil(t, err) 194 | assert.Equal(t, "gpio1/active_low", mrw.v.prevPath) 195 | assert.Equal(t, []byte("0"), mrw.v.readVal) 196 | } 197 | 198 | func TestEdge(t *testing.T) { 199 | p := NewPin(1, "gpio1", new(watch)) 200 | 201 | tests := []struct { 202 | val string 203 | expected Edge 204 | err error 205 | }{ 206 | {"rising\n", RisingEdge, nil}, 207 | {"falling\n", FallingEdge, nil}, 208 | {"none\n", NoneEdge, nil}, 209 | {"both\n", BothEdge, nil}, 210 | {"not-a-valid-value", NoneEdge, errors.New("not a known value: 'not-a-va'")}, 211 | {"", NoneEdge, errors.New("not enough bytes to read")}, 212 | } 213 | for _, test := range tests { 214 | mrw := mockReaderWriter{&testValues{readVal: []byte(test.val)}} 215 | p.rwHelper = mrw 216 | val, err := p.Edge() 217 | assert.Equal(t, test.err, err) 218 | assert.Equal(t, test.expected, val) 219 | assert.Equal(t, "gpio1/edge", mrw.v.prevPath) 220 | } 221 | 222 | p.rwHelper = mockReaderWriter{&testValues{mockErr: errors.New("error")}} 223 | val, err := p.Edge() 224 | assert.Equal(t, "error", err.Error()) 225 | assert.Equal(t, NoneEdge, val) 226 | } 227 | 228 | func TestSetEdge(t *testing.T) { 229 | // TODO: Find a way to mock opening files 230 | } 231 | 232 | func TestExport(t *testing.T) { 233 | p := NewPin(1, "gpio1", new(watch)) 234 | mrw := mockReaderWriter{&testValues{}} 235 | p.rwHelper = mrw 236 | 237 | tests := []struct { 238 | err error 239 | // mockErr is the error the mock is going to return 240 | mockErr error 241 | }{ 242 | {nil, nil}, 243 | {nil, fmt.Errorf("write %v/export: device or resource busy", basePath)}, 244 | {errors.New("error"), errors.New("error")}, 245 | } 246 | for _, test := range tests { 247 | mrw := mockReaderWriter{&testValues{mockErr: test.mockErr}} 248 | p.rwHelper = mrw 249 | 250 | err := p.Export() 251 | assert.Equal(t, test.err, err) 252 | } 253 | } 254 | 255 | func TestUnexport(t *testing.T) { 256 | p := NewPin(1, "gpio1", new(watch)) 257 | mrw := mockReaderWriter{&testValues{}} 258 | p.rwHelper = mrw 259 | 260 | tests := []struct { 261 | err error 262 | // mockErr is the error the mock is going to return 263 | mockErr error 264 | }{ 265 | {nil, nil}, 266 | {errors.New("error"), errors.New("error")}, 267 | } 268 | for _, test := range tests { 269 | mrw := mockReaderWriter{&testValues{mockErr: test.mockErr}} 270 | p.rwHelper = mrw 271 | 272 | err := p.Unexport() 273 | assert.Equal(t, test.err, err) 274 | } 275 | 276 | } 277 | -------------------------------------------------------------------------------- /gpio/watch.go: -------------------------------------------------------------------------------- 1 | // +build linux 2 | 3 | package gpio 4 | 5 | import ( 6 | "fmt" 7 | "os" 8 | "sync" 9 | "syscall" 10 | ) 11 | 12 | type watchCallback struct { 13 | initial bool 14 | callback func() 15 | } 16 | 17 | // Watcher watches files for events and executes a callback when an event occurs. 18 | type Watcher interface { 19 | Watch() error 20 | StopWatch() 21 | AddEvent(fpnt int, callback func()) error 22 | AddFile(file *os.File) 23 | Close() error 24 | } 25 | 26 | // watch is the implementation of Watcher. It is used to watch gpio files for 27 | // changes and handle events assiociated with those files. 28 | type watch struct { 29 | // sysH is used to make all the syscalls, this way all syscalls can be 30 | // mocked during tests. 31 | sysH syscaller 32 | // File descriptor for epoll 33 | fd int 34 | callbacks map[int]*watchCallback 35 | 36 | // Keep a reference to the files, otherwise it might get garbage collected, 37 | // which causes epoll not recieveing any events. 38 | files []*os.File 39 | run bool 40 | m sync.RWMutex 41 | } 42 | 43 | // NewWatcher Creates a new Watcher. 44 | func NewWatcher() (Watcher, error) { 45 | return newWatch(new(syscallHelper)) 46 | } 47 | 48 | // newWatch creates a new watch with the given syscall helper. This function is 49 | // useful for testing, because it allows the creation of Watchers with a 50 | // custom sycall handler 51 | func newWatch(sysH syscaller) (*watch, error) { 52 | // Open an epoll file descriptor. 53 | // EpollCreate1 does the same thing as epoll create, except with a 0 as 54 | // argument the obsolete size argument is dropped. 55 | epollFD, err := sysH.EpollCreate1(0) 56 | if err != nil { 57 | return nil, fmt.Errorf("Unable to create epoll FD: %v", err.Error()) 58 | } 59 | 60 | w := &watch{ 61 | sysH: sysH, 62 | fd: epollFD, 63 | callbacks: make(map[int]*watchCallback), 64 | m: sync.RWMutex{}, 65 | } 66 | return w, nil 67 | } 68 | 69 | // Watch handles incoming epoll events. 70 | func (w *watch) Watch() error { 71 | w.run = true 72 | // maxEvents is the maximum of events handled at once. 73 | w.m.RLock() 74 | maxEvents := len(w.callbacks) 75 | w.m.RUnlock() 76 | 77 | if maxEvents == 0 { 78 | // At least one event must be handeld. 79 | maxEvents = 1 80 | } 81 | events := make([]syscall.EpollEvent, maxEvents) 82 | for w.run { 83 | // The last argument is the timeout, the timeout specifies how long the call will block, 84 | // Setting a timeout of -1 will make it block indefinitely. 85 | numEvents, err := w.sysH.EpollWait(w.fd, events, -1) 86 | if err != nil { 87 | if err == syscall.EAGAIN { 88 | continue 89 | } 90 | return fmt.Errorf("stopping watch loop: %v", err) 91 | } 92 | for i := 0; i < numEvents; i++ { 93 | go w.handleEvent(int(events[i].Fd)) 94 | } 95 | } 96 | return nil 97 | } 98 | 99 | // StopWatch stops the watcher after next event. 100 | func (w *watch) StopWatch() { 101 | w.run = false 102 | } 103 | 104 | // handleEvent runs the callback funcion if one has been registered for a file. 105 | // The first event is ignored, because this is the event fired when this file 106 | // is first added . 107 | func (w *watch) handleEvent(fd int) { 108 | w.m.Lock() 109 | wcb, exists := w.callbacks[fd] 110 | w.m.Unlock() 111 | 112 | if exists { 113 | if !wcb.initial { 114 | wcb.callback() 115 | } 116 | w.m.Lock() 117 | wcb.initial = false 118 | w.m.Unlock() 119 | } 120 | } 121 | 122 | func (w *watch) addCallback(fpntr int, callback func()) { 123 | w.m.Lock() 124 | w.callbacks[fpntr] = &watchCallback{ 125 | true, 126 | callback, 127 | } 128 | w.m.Unlock() 129 | } 130 | 131 | func (w *watch) AddFile(file *os.File) { 132 | w.m.Lock() 133 | w.files = append(w.files, file) 134 | w.m.Unlock() 135 | } 136 | 137 | func (w *watch) AddEvent(fpntr int, callback func()) error { 138 | var event syscall.EpollEvent 139 | event.Events = syscall.EPOLLIN | (syscall.EPOLLET & 0xffffffff) 140 | event.Fd = int32(fpntr) 141 | 142 | // An application that employs the EPOLLET flag should use nonblocking 143 | // file descriptors to avoid having a blocking read or write starve a 144 | // task that is handling multiple file descriptors. 145 | // - http://man7.org/linux/man-pages/man7/epoll.7.html 146 | if err := w.sysH.SetNonblock(fpntr, true); err != nil { 147 | return err 148 | } 149 | 150 | if err := w.sysH.EpollCtl(w.fd, syscall.EPOLL_CTL_ADD, fpntr, &event); err != nil { 151 | return err 152 | } 153 | w.addCallback(fpntr, callback) 154 | return nil 155 | } 156 | 157 | func (w *watch) Close() error { 158 | return syscall.Close(w.fd) 159 | } 160 | 161 | // syscaller is an interface specifing the syscall-based functions watcher needs 162 | // to watch. This way the syscalls can be replaced with a mock during tests. 163 | type syscaller interface { 164 | EpollCreate1(flag int) (fd int, err error) 165 | EpollWait(epfd int, events []syscall.EpollEvent, msec int) (n int, err error) 166 | SetNonblock(fd int, nonblocking bool) (err error) 167 | EpollCtl(epfd int, op int, fd int, event *syscall.EpollEvent) (err error) 168 | } 169 | 170 | // syscallHelper implements the syscaller interface and should be used to make 171 | // syscalls in watcher. 172 | type syscallHelper struct{} 173 | 174 | func (syscallHelper) EpollCreate1(flag int) (fd int, err error) { 175 | return syscall.EpollCreate1(flag) 176 | } 177 | 178 | func (syscallHelper) EpollWait(epfd int, events []syscall.EpollEvent, msec int) (n int, err error) { 179 | return syscall.EpollWait(epfd, events, msec) 180 | } 181 | 182 | func (syscallHelper) SetNonblock(fd int, nonblocking bool) (err error) { 183 | return syscall.SetNonblock(fd, nonblocking) 184 | } 185 | 186 | func (syscallHelper) EpollCtl(epfd int, op int, fd int, event *syscall.EpollEvent) (err error) { 187 | return syscall.EpollCtl(epfd, op, fd, event) 188 | } 189 | -------------------------------------------------------------------------------- /gpio/watch_test.go: -------------------------------------------------------------------------------- 1 | package gpio 2 | 3 | import ( 4 | "errors" 5 | "syscall" 6 | "testing" 7 | 8 | "github.com/stretchr/testify/assert" 9 | ) 10 | 11 | type mockSys struct { 12 | ecErr error 13 | ewErr error 14 | snbErr error 15 | ectlbErr error 16 | 17 | // the Watch tests needs to be able to replace EpollWait completely 18 | eWaitFn func(int, []syscall.EpollEvent, int) (int, error) 19 | } 20 | 21 | func (m *mockSys) EpollCreate1(flag int) (fd int, err error) { 22 | return 15, m.ecErr 23 | } 24 | 25 | func (m *mockSys) EpollWait(epfd int, events []syscall.EpollEvent, msec int) (n int, err error) { 26 | if m.eWaitFn != nil { 27 | return m.eWaitFn(epfd, events, msec) 28 | } 29 | return 0, m.ewErr 30 | } 31 | 32 | func (m *mockSys) SetNonblock(fd int, nonblocking bool) (err error) { 33 | return m.snbErr 34 | } 35 | 36 | func (m *mockSys) EpollCtl(epfd int, op int, fd int, event *syscall.EpollEvent) (err error) { 37 | return m.ectlbErr 38 | } 39 | 40 | func TestNewWatch(t *testing.T) { 41 | w, err := newWatch(&mockSys{}) 42 | assert.Nil(t, err) 43 | assert.Equal(t, 15, w.fd) 44 | 45 | w, err = newWatch(&mockSys{ecErr: errors.New("error")}) 46 | assert.Equal(t, "Unable to create epoll FD: error", err.Error()) 47 | assert.Nil(t, w) 48 | } 49 | 50 | func TestHandleEvent(t *testing.T) { 51 | w, _ := newWatch(&mockSys{}) 52 | 53 | called := 0 54 | w.addCallback(1, func() { called++ }) 55 | tests := []struct { 56 | fd int 57 | called int 58 | initial bool 59 | }{ 60 | {1, 0, true}, 61 | {1, 1, false}, 62 | {1, 2, false}, 63 | {2, 2, false}, 64 | {6, 2, false}, 65 | } 66 | for _, test := range tests { 67 | if cb, ok := w.callbacks[test.fd]; ok { 68 | assert.Equal(t, test.initial, cb.initial) 69 | } 70 | w.handleEvent(test.fd) 71 | assert.Equal(t, test.called, called) 72 | } 73 | } 74 | 75 | func TestAddEvent(t *testing.T) { 76 | w, _ := newWatch(&mockSys{}) 77 | 78 | tests := []struct { 79 | fd int 80 | snbErr error 81 | ctlErr error 82 | expected error 83 | expectedLen int 84 | }{ 85 | {1, nil, nil, nil, 1}, 86 | {1, errors.New("err"), nil, errors.New("err"), 1}, 87 | {1, nil, errors.New("err"), errors.New("err"), 1}, 88 | {1, errors.New("err1"), errors.New("err2"), errors.New("err1"), 1}, 89 | {2, errors.New("err"), nil, errors.New("err"), 1}, 90 | {2, nil, errors.New("err"), errors.New("err"), 1}, 91 | {2, errors.New("err1"), errors.New("err2"), errors.New("err1"), 1}, 92 | {2, nil, nil, nil, 2}, 93 | } 94 | 95 | for _, test := range tests { 96 | w.sysH = &mockSys{snbErr: test.snbErr, ectlbErr: test.ctlErr} 97 | err := w.AddEvent(test.fd, func() {}) 98 | assert.Equal(t, test.expected, err) 99 | assert.Equal(t, test.expectedLen, len(w.callbacks)) 100 | if err == nil { 101 | assert.Equal(t, true, w.callbacks[test.fd].initial) 102 | } 103 | } 104 | } 105 | 106 | func TestWatch(t *testing.T) { 107 | w, _ := newWatch(&mockSys{}) 108 | 109 | tests := []struct { 110 | callbacks int 111 | err error 112 | expectedMax int 113 | expectedErr error 114 | }{ 115 | { 116 | 1, nil, 1, nil, 117 | }, 118 | { 119 | 0, nil, 1, nil, 120 | }, 121 | { 122 | 1, syscall.EAGAIN, 1, nil, 123 | }, 124 | { 125 | 0, errors.New("error"), 1, errors.New("stopping watch loop: error"), 126 | }, 127 | } 128 | 129 | for _, test := range tests { 130 | for i := 0; i < test.callbacks; i++ { 131 | w.addCallback(i, func() {}) 132 | } 133 | eWaitFn := func(epfd int, events []syscall.EpollEvent, msec int) (int, error) { 134 | assert.Equal(t, test.expectedMax, len(events)) 135 | // Stopping in the waitfunc ensures the loop only loops once 136 | w.StopWatch() 137 | return 1, test.err 138 | } 139 | w.sysH = &mockSys{eWaitFn: eWaitFn} 140 | err := w.Watch() 141 | assert.Equal(t, test.expectedErr, err) 142 | } 143 | } 144 | -------------------------------------------------------------------------------- /i2c/max/README.md: -------------------------------------------------------------------------------- 1 | [![godoc](https://img.shields.io/badge/godoc-reference-blue.svg?style=flat)](https://godoc.org/github.com/AdvancedClimateSystems/io/i2c/max) 2 | 3 | # MAX 4 | 5 | Package microchip implements drivers for a few I2C controlled IC's 6 | produced by [Maxim Integrated](https://www.maximintegrated.com). This package 7 | relies on [x/exp/io/spi](https://godoc.org/golang.org/x/exp/io/i2c). 8 | 9 | Drivers for the following IC's are implemented: 10 | 11 | * [MAX5813](https://www.maximintegrated.com/en/products/analog/data-converters/digital-to-analog-converters/MAX5813.html) 12 | * [MAX5814](https://www.maximintegrated.com/en/products/analog/data-converters/digital-to-analog-converters/MAX5814.html) 13 | * [MAX5815](https://www.maximintegrated.com/en/products/analog/data-converters/digital-to-analog-converters/MAX5815.html) 14 | 15 | Sample usage: 16 | 17 | ```go 18 | package main 19 | 20 | import ( 21 | "fmt" 22 | 23 | "github.com/advancedclimatesystems/io/i2c/max" 24 | "golang.org/x/exp/io/i2c" 25 | ) 26 | 27 | func main() { 28 | d, err := i2c.Open(&i2c.Devfs{ 29 | Dev: "/dev/i2c-0", 30 | }, 0x1c) 31 | 32 | if err != nil { 33 | panic(fmt.Sprintf("failed to open device: %v", err)) 34 | } 35 | defer d.Close() 36 | 37 | 38 | // 2.5V is the input reference of the DAC. 39 | dac, err := max.NewMAX5813(d, 2.5) 40 | 41 | if err != nil { 42 | panic(fmt.Sprintf("failed to create MAX5813: %v", err)) 43 | } 44 | 45 | // Set output of channel 1 to 1.3V. 46 | if err := dac.SetVoltage(1.3, 1); err != nil { 47 | panic(fmt.Sprintf("failed to set voltage: %v", err)) 48 | } 49 | 50 | // It's also possible to set output of a channel with digital output code. 51 | if err := dac.SetInputCode(128, 1); err != nil { 52 | panic(fmt.Sprintf("failed to set voltage using output code: %v", err)) 53 | } 54 | } 55 | ``` 56 | -------------------------------------------------------------------------------- /i2c/max/max518x.go: -------------------------------------------------------------------------------- 1 | // Package max contains drivers for IC's produced by Maxim Integrated. 2 | // 3 | // MAX581x 4 | // 5 | // The implemententation for the MAX5813, MAX5814 and MAX5815 only implement 6 | // the REF and CODEn_LOADn commands. The commands CODEn, LOADn, CODEn_LOAD_ALL, 7 | // POWER, SW_CLEAR, SW_RESET, CONFIG, CODE_ALL, LOAD_ALL and CODE_ALL, 8 | // CODE_ALL_LOAD_ALL are not implemented. 9 | package max 10 | 11 | import ( 12 | "fmt" 13 | "math" 14 | 15 | "golang.org/x/exp/io/i2c" 16 | ) 17 | 18 | const ( 19 | // CODEn_LOADn simultaneously writes data to the selected CODE 20 | // register(s) while updating selected DAC register(s). 21 | codenLoadn = 0x30 22 | ) 23 | 24 | // MAX5813 is a 4 channel DAC with a resolution of 8 bits. The datasheet is 25 | // here: https://datasheets.maximintegrated.com/en/ds/MAX5813-MAX5815.pdf 26 | type MAX5813 struct { 27 | max581x 28 | } 29 | 30 | // NewMAX5813 returns a new instance of MAX5813. 31 | func NewMAX5813(conn *i2c.Device, vref float64) (*MAX5813, error) { 32 | m := &MAX5813{ 33 | max581x{ 34 | conn: conn, 35 | resolution: 8, 36 | }, 37 | } 38 | 39 | if err := m.SetVref(vref); err != nil { 40 | return nil, err 41 | } 42 | 43 | return m, nil 44 | } 45 | 46 | // MAX5814 is a 4 channel DAC with a resolution of 10 bits. The datasheet is 47 | // here: https://datasheets.maximintegrated.com/en/ds/MAX5813-MAX5815.pdf 48 | type MAX5814 struct { 49 | max581x 50 | } 51 | 52 | // NewMAX5814 returns a new instance of MAX5814. 53 | func NewMAX5814(conn *i2c.Device, vref float64) (*MAX5814, error) { 54 | m := &MAX5814{ 55 | max581x{ 56 | conn: conn, 57 | resolution: 10, 58 | }, 59 | } 60 | 61 | if err := m.SetVref(vref); err != nil { 62 | return nil, err 63 | } 64 | 65 | return m, nil 66 | } 67 | 68 | // MAX5815 is a 4 channel DAC with a resolution of 12 bits. The datasheet is 69 | // here: https://datasheets.maximintegrated.com/en/ds/MAX5813-MAX5815.pdf 70 | type MAX5815 struct { 71 | max581x 72 | } 73 | 74 | // NewMAX5815 returns a new instance of MAX5814. 75 | func NewMAX5815(conn *i2c.Device, vref float64) (*MAX5815, error) { 76 | m := &MAX5815{ 77 | max581x{ 78 | conn: conn, 79 | resolution: 12, 80 | }, 81 | } 82 | 83 | if err := m.SetVref(vref); err != nil { 84 | return nil, err 85 | } 86 | 87 | return m, nil 88 | } 89 | 90 | type max581x struct { 91 | conn *i2c.Device 92 | vref float64 93 | resolution int 94 | } 95 | 96 | // SetVoltage set output voltage of channel. Using the Vref the input code is 97 | // calculated and then SetInputCode is called. 98 | func (m max581x) SetVoltage(v float64, channel int) error { 99 | code := v * (math.Pow(2, float64(m.resolution)) - 1) / m.vref 100 | return m.SetInputCode(int(code), channel) 101 | } 102 | 103 | // SetInputCode writes the digital input code to the DAC using the CODEn_LOADn 104 | // command. 105 | func (m max581x) SetInputCode(code, channel int) error { 106 | if channel < 0 || channel > 3 { 107 | return fmt.Errorf("%d is not a valid channel", channel) 108 | } 109 | 110 | max := int(math.Pow(2, float64(m.resolution))) 111 | if code < 0 || code >= max { 112 | return fmt.Errorf("digital input code %d is out of range of 0 <= code < %d", code, int(max)) 113 | } 114 | 115 | // The requests is 3 bytes long. Byte 1 is the command, byte 2 and 3 116 | // contain the output code. 117 | cmd := byte(codenLoadn | channel) 118 | msb := byte((code >> uint(m.resolution-8)) & 0xFF) 119 | lsb := byte((code << uint(8-(m.resolution-8))) & 0xFF) 120 | 121 | return m.conn.Write([]byte{cmd, msb, lsb}) 122 | } 123 | 124 | // Vref sets the global reference for all channels. The device can use either 125 | // an external reference or a internel reference, this depends on the wiring 126 | // of the IC. Allowed values for the internel reference are 2.5V, 2.048V and 127 | // 4.096V. If this function is called with one of these value the internel 128 | // reference is set to this value using the REF command. For any other value 129 | // the channels will use the input reference is equal to the 130 | func (m *max581x) SetVref(v float64) error { 131 | m.vref = v 132 | cmd := 0x70 133 | 134 | switch v { 135 | case 2.5: 136 | cmd = cmd | 5 137 | case 2.048: 138 | cmd = cmd | 6 139 | case 4.096: 140 | cmd = cmd | 7 141 | } 142 | 143 | out := []byte{byte(cmd), 0, 0} 144 | return m.conn.Write(out) 145 | } 146 | 147 | // Conn returns the connection to the I2C device. 148 | func (m *max581x) Conn() *i2c.Device { 149 | return m.conn 150 | } 151 | -------------------------------------------------------------------------------- /i2c/max/max518x_test.go: -------------------------------------------------------------------------------- 1 | package max 2 | 3 | import ( 4 | "fmt" 5 | "testing" 6 | 7 | "github.com/advancedclimatesystems/io/dac" 8 | "github.com/advancedclimatesystems/io/iotest" 9 | "github.com/stretchr/testify/assert" 10 | "golang.org/x/exp/io/i2c" 11 | ) 12 | 13 | func TestDACinterface(t *testing.T) { 14 | assert.Implements(t, (*dac.DAC)(nil), new(MAX5813)) 15 | assert.Implements(t, (*dac.DAC)(nil), new(MAX5814)) 16 | assert.Implements(t, (*dac.DAC)(nil), new(MAX5815)) 17 | } 18 | 19 | func TestNewMAX581x(t *testing.T) { 20 | conn, _ := i2c.Open(iotest.NewI2CDriver(iotest.NewI2CConn()), 0x1) 21 | 22 | max5813, _ := NewMAX5813(conn, 3) 23 | assert.Equal(t, 8, max5813.resolution) 24 | 25 | max5814, _ := NewMAX5814(conn, 3) 26 | assert.Equal(t, 10, max5814.resolution) 27 | 28 | max5815, _ := NewMAX5815(conn, 3) 29 | assert.Equal(t, 12, max5815.resolution) 30 | } 31 | 32 | func TestMAX581xSetVref(t *testing.T) { 33 | data := make(chan []byte, 2) 34 | c := iotest.NewI2CConn() 35 | c.TxFunc(func(w, _ []byte) error { 36 | data <- w 37 | return nil 38 | }) 39 | 40 | conn, _ := i2c.Open(iotest.NewI2CDriver(c), 0x1) 41 | 42 | m := max581x{ 43 | conn: conn, 44 | resolution: 8, 45 | } 46 | 47 | var tests = []struct { 48 | vref float64 49 | expected []byte 50 | }{ 51 | {2.5, []byte{0x75, 0, 0}}, 52 | {2.048, []byte{0x76, 0, 0}}, 53 | {4.096, []byte{0x77, 0, 0}}, 54 | } 55 | 56 | for _, test := range tests { 57 | m.SetVref(test.vref) 58 | assert.Equal(t, test.expected, <-data) 59 | } 60 | 61 | } 62 | 63 | func TestMAX581xSetVoltage(t *testing.T) { 64 | data := make(chan []byte, 2) 65 | c := iotest.NewI2CConn() 66 | c.TxFunc(func(w, _ []byte) error { 67 | data <- w 68 | return nil 69 | }) 70 | 71 | conn, _ := i2c.Open(iotest.NewI2CDriver(c), 0x1) 72 | m := max581x{ 73 | conn: conn, 74 | } 75 | 76 | var tests = []struct { 77 | resolution int 78 | vref float64 79 | voltage float64 80 | channel int 81 | expected []byte 82 | }{ 83 | {8, 2.5, 2.5, 1, []byte{0x31, 0xff, 0}}, 84 | {8, 5, 2.5, 2, []byte{0x32, 0x7f, 0}}, 85 | {8, 5, 0, 2, []byte{0x32, 0, 0}}, 86 | 87 | {10, 5, 5, 2, []byte{0x32, 0xff, 0xc0}}, 88 | {10, 5, 2.5, 2, []byte{0x32, 0x7f, 0xc0}}, 89 | {10, 5, 0, 2, []byte{0x32, 0, 0}}, 90 | 91 | {12, 2.5, 2.5, 3, []byte{0x33, 0xff, 0xf0}}, 92 | {12, 5, 2.5, 3, []byte{0x33, 0x7f, 0xf0}}, 93 | {12, 10, 2, 3, []byte{0x33, 0x33, 0x30}}, 94 | } 95 | 96 | for _, test := range tests { 97 | m.resolution = test.resolution 98 | m.vref = test.vref 99 | 100 | err := m.SetVoltage(test.voltage, test.channel) 101 | if err != nil { 102 | t.Fatal(err) 103 | } 104 | 105 | assert.Equal(t, test.expected, <-data) 106 | 107 | } 108 | } 109 | 110 | func TestMAX581xConn(t *testing.T) { 111 | c, _ := i2c.Open(iotest.NewI2CDriver(iotest.NewI2CConn()), 0x1) 112 | dac, _ := NewMAX5813(c, 2.048) 113 | assert.Equal(t, c, dac.Conn()) 114 | } 115 | 116 | // TestMAX581xSetInputCodeWithInvalidChannel calls dac.SetInputCode with a 117 | // input code that lies outside the range of the DAC. 118 | func TestMAX581xSetInputCodeWithInvalidCode(t *testing.T) { 119 | var tests = []struct { 120 | dac dac.DAC 121 | code int 122 | }{ 123 | {MAX5813{}, -1}, 124 | {MAX5813{}, 256}, 125 | {MAX5814{}, -1}, 126 | {MAX5814{}, 1024}, 127 | {MAX5815{}, -1}, 128 | {MAX5815{}, 4096}, 129 | } 130 | 131 | for _, test := range tests { 132 | assert.EqualError( 133 | t, 134 | test.dac.SetInputCode(test.code, 1), 135 | fmt.Sprintf("digital input code %d is out of range of 0 <= code < 1", test.code)) 136 | } 137 | } 138 | 139 | // TestMAX581xSetInputCodeWithInvalidChannel calls dac.SetInputCode with a 140 | // channel that isn't in the range of the DAC. 141 | func TestMAX581xSetInputCodeWithInvalidChannel(t *testing.T) { 142 | dac := max581x{} 143 | 144 | for _, channel := range []int{-1, 4} { 145 | assert.EqualError( 146 | t, 147 | dac.SetInputCode(512, channel), 148 | fmt.Sprintf("%d is not a valid channel", channel)) 149 | } 150 | } 151 | 152 | // TestMAX581xWithFailingConnection test if all DAC's return errors when the 153 | // connection fails. 154 | func TestMAX581xWithFailingConnection(t *testing.T) { 155 | c := iotest.NewI2CConn() 156 | c.TxFunc(func(w, _ []byte) error { 157 | return fmt.Errorf("som error occured") 158 | }) 159 | conn, _ := i2c.Open(iotest.NewI2CDriver(c), 0x1) 160 | 161 | _, err := NewMAX5813(conn, 2.048) 162 | assert.NotNil(t, err) 163 | 164 | _, err = NewMAX5814(conn, 2.048) 165 | assert.NotNil(t, err) 166 | 167 | _, err = NewMAX5815(conn, 2.048) 168 | assert.NotNil(t, err) 169 | 170 | dac := max581x{ 171 | conn: conn, 172 | vref: 2.048, 173 | resolution: 8, 174 | } 175 | 176 | assert.NotNil(t, dac.SetInputCode(512, 1)) 177 | } 178 | 179 | func ExampleMAX5813() { 180 | d, err := i2c.Open(&i2c.Devfs{ 181 | Dev: "/dev/i2c-0", 182 | }, 0x1c) 183 | 184 | if err != nil { 185 | panic(fmt.Sprintf("failed to open device: %v", err)) 186 | } 187 | defer d.Close() 188 | 189 | // 2.5V is the input reference of the DAC. 190 | dac, err := NewMAX5813(d, 2.5) 191 | 192 | if err != nil { 193 | panic(fmt.Sprintf("failed to create MAX5813: %v", err)) 194 | } 195 | 196 | // Set output of channel 1 to 1.3V. 197 | if err := dac.SetVoltage(1.3, 1); err != nil { 198 | panic(fmt.Sprintf("failed to set voltage: %v", err)) 199 | } 200 | 201 | // It's also possible to set output of a channel with digital output code. 202 | if err := dac.SetInputCode(128, 1); err != nil { 203 | panic(fmt.Sprintf("failed to set voltage using output code: %v", err)) 204 | } 205 | } 206 | -------------------------------------------------------------------------------- /i2c/microchip/README.md: -------------------------------------------------------------------------------- 1 | [![godoc](https://img.shields.io/badge/godoc-reference-blue.svg?style=flat)](https://godoc.org/github.com/AdvancedClimateSystems/io/i2c/microchip) 2 | 3 | # Microchip 4 | 5 | Package microchip implements drivers for I2C controlled IC's 6 | produced by [Microchip](http://www.microchip.com). 7 | 8 | Drivers for the following IC's are implemented: 9 | 10 | * [MCP4725](http://www.microchip.com/wwwproducts/DevicePrint/en/MCP4725?httproute=True) 11 | 12 | Sample usage: 13 | 14 | 15 | ```go 16 | package main 17 | 18 | import ( 19 | "fmt" 20 | 21 | "github.com/advancedclimatesystems/io/i2c/microchip" 22 | "golang.org/x/exp/io/i2c" 23 | ) 24 | 25 | func main() { 26 | d, err := i2c.Open(&i2c.Devfs{ 27 | Dev: "/dev/i2c-1", 28 | }, 0x60) 29 | 30 | if err != nil { 31 | panic(fmt.Sprintf("failed to open device: %v", err)) 32 | } 33 | defer d.Close() 34 | 35 | // Reference voltage is 2.7V. 36 | dac, err := microchip.NewMCP4725(d, 2.7) 37 | 38 | if err != nil { 39 | panic(fmt.Sprintf("failed to create MCP4725: %v", err)) 40 | } 41 | 42 | // Set output of channel 1 to 1.3V. The MCP4725 has only 1 channel, 43 | // select other channels results in an error. 44 | if err := dac.SetVoltage(3, 1); err != nil { 45 | panic(fmt.Sprintf("failed to set voltage: %v", err)) 46 | } 47 | 48 | // It's also possible to set output of a channel with digital output 49 | // code. The value must be in range of 0 till 4096. 50 | if err := dac.SetInputCode(4095, 1); err != nil { 51 | panic(fmt.Sprintf("failed to set voltage using output code: %v", err)) 52 | } 53 | } 54 | ``` 55 | -------------------------------------------------------------------------------- /i2c/microchip/mcp4725.go: -------------------------------------------------------------------------------- 1 | // Package microchip implements drivers for a few I2C controlled chips produced 2 | // by Microchip. 3 | package microchip 4 | 5 | import ( 6 | "fmt" 7 | 8 | "golang.org/x/exp/io/i2c" 9 | ) 10 | 11 | // The MCP4725 has a 14 bit wide EEPROM to store configuration bits (2 bits) 12 | // and DAC input data (12 bits) 13 | // 14 | // The MCP4275 also has a 19 bits DAC register. The master can read/write the 15 | // DAC register or EEPROM using i2c interface. 16 | // 17 | // The MCP4725 device address contains four fixed bits (1100 = device code) and 18 | // three address bits (A2, A1, A0). The A2 and A1 bits are hard-wired during 19 | // manufacturing, and the A0 bit is determined by the logic state of AO pin. 20 | // 21 | // The MCP4725 has 2 modes of operation: normal mode and power-down mode. This 22 | // driver only supports normal mode. 23 | // 24 | // The datasheet of the device is here: 25 | // http://ww1.microchip.com/downloads/en/DeviceDoc/22039d.pdf 26 | type MCP4725 struct { 27 | conn *i2c.Device 28 | vref float64 29 | 30 | Address int 31 | } 32 | 33 | // NewMCP4725 returns a new instance of MCP4725. 34 | func NewMCP4725(conn *i2c.Device, vref float64) (*MCP4725, error) { 35 | return &MCP4725{ 36 | conn: conn, 37 | vref: vref, 38 | }, nil 39 | } 40 | 41 | // SetVoltage sets voltage of the only channel of the MCP4725. The channel 42 | // parameter is required in the signature of the function to be conform with 43 | // the dac.DAC interface. Because the MCP4725 has only 1 channel it's only 44 | // allowed value is 1. 45 | func (m MCP4725) SetVoltage(v float64, channel int) error { 46 | code := v * 4095 / m.vref 47 | return m.SetInputCode(int(code), channel) 48 | } 49 | 50 | // SetInputCode sets voltage of the only channel of the MCP4725. The channel 51 | // parameter is required in the signature of the function to be conform with 52 | // the dac.DAC interface. Because the MCP4725 has only 1 channel it's only 53 | // allowed value is 1. 54 | func (m MCP4725) SetInputCode(code, channel int) error { 55 | if channel != 1 { 56 | return fmt.Errorf("channel %d is invalid, MCP4725 has only 1 channel", channel) 57 | } 58 | 59 | if code < 0 || code >= 4096 { 60 | return fmt.Errorf("digital input code %d is out of range of 0 <= code < 4096", code) 61 | } 62 | 63 | out := []byte{byte(code >> byte(8)), byte(code & 0xFF)} 64 | 65 | if err := m.conn.Write(out); err != nil { 66 | return fmt.Errorf("failed to write output code %d: %v", code, err) 67 | } 68 | 69 | return nil 70 | } 71 | -------------------------------------------------------------------------------- /i2c/microchip/mcp4725_test.go: -------------------------------------------------------------------------------- 1 | package microchip 2 | 3 | import ( 4 | "errors" 5 | "fmt" 6 | "testing" 7 | 8 | "github.com/advancedclimatesystems/io/dac" 9 | "github.com/advancedclimatesystems/io/iotest" 10 | "github.com/stretchr/testify/assert" 11 | "golang.org/x/exp/io/i2c" 12 | ) 13 | 14 | func TestDACinterface(t *testing.T) { 15 | assert.Implements(t, (*dac.DAC)(nil), new(MCP4725)) 16 | } 17 | 18 | func TestMCP4725WithValidVoltages(t *testing.T) { 19 | data := make(chan []byte, 2) 20 | c := iotest.NewI2CConn() 21 | 22 | c.TxFunc(func(w, _ []byte) error { 23 | data <- w 24 | return nil 25 | }) 26 | 27 | conn, _ := i2c.Open(iotest.NewI2CDriver(c), 0x1) 28 | 29 | var tests = []struct { 30 | vref float64 31 | voltage float64 32 | expected []byte 33 | }{ 34 | {2.7, 1.73, []byte{0xa, 0x3f}}, 35 | {2.7, 2.6999, []byte{0x0f, 0xfe}}, 36 | {2.7, 0, []byte{0x0, 0x0}}, 37 | {5.5, 1.22, []byte{0x3, 0x8c}}, 38 | {5.5, 0.73, []byte{0x2, 0x1f}}, 39 | } 40 | 41 | for _, test := range tests { 42 | m, err := NewMCP4725(conn, test.vref) 43 | assert.Nil(t, err) 44 | 45 | err = m.SetVoltage(test.voltage, 1) 46 | assert.Equal(t, test.expected, <-data) 47 | assert.Nil(t, err) 48 | } 49 | } 50 | 51 | func TestMCP4725WithInValidVoltages(t *testing.T) { 52 | conn, _ := i2c.Open(iotest.NewI2CDriver(iotest.NewI2CConn()), 0x1) 53 | m, _ := NewMCP4725(conn, 2.7) 54 | 55 | voltages := []float64{-1, 28.1} 56 | for _, v := range voltages { 57 | err := m.SetVoltage(v, 1) 58 | assert.NotNil(t, err) 59 | } 60 | } 61 | 62 | func TestMCP4725WithInvalidChannel(t *testing.T) { 63 | conn, _ := i2c.Open(iotest.NewI2CDriver(iotest.NewI2CConn()), 0x1) 64 | m, _ := NewMCP4725(conn, 2.7) 65 | 66 | channels := []int{-1, 0, 2, 28} 67 | for _, c := range channels { 68 | err := m.SetVoltage(1, c) 69 | assert.NotNil(t, err) 70 | } 71 | } 72 | 73 | func TestMCP4725WithFailingConnection(t *testing.T) { 74 | c := iotest.NewI2CConn() 75 | c.TxFunc(func(_, _ []byte) error { return errors.New("Is there a officer, problem?") }) 76 | conn, _ := i2c.Open(iotest.NewI2CDriver(c), 0x1) 77 | 78 | m, _ := NewMCP4725(conn, 2.7) 79 | err := m.SetVoltage(1, 1) 80 | 81 | assert.NotNil(t, err) 82 | } 83 | 84 | func ExampleMCP4725() { 85 | d, err := i2c.Open(&i2c.Devfs{ 86 | Dev: "/dev/i2c-0", 87 | }, 0x61) 88 | 89 | if err != nil { 90 | panic(fmt.Sprintf("failed to open device: %v", err)) 91 | } 92 | defer d.Close() 93 | 94 | // Reference voltage is 2.7V. 95 | dac, err := NewMCP4725(d, 2.7) 96 | 97 | if err != nil { 98 | panic(fmt.Sprintf("failed to create MCP4725: %v", err)) 99 | } 100 | 101 | // Set output of channel 1 to 1.3V. The MCP4725 has only 1 channel, 102 | // select other channels results in an error. 103 | if err := dac.SetVoltage(3, 1); err != nil { 104 | panic(fmt.Sprintf("failed to set voltage: %v", err)) 105 | } 106 | 107 | // It's also possible to set output of a channel with digital output 108 | // code. The value must be in range of 0 till 4096. 109 | if err := dac.SetInputCode(4095, 1); err != nil { 110 | panic(fmt.Sprintf("failed to set voltage using output code: %v", err)) 111 | } 112 | } 113 | -------------------------------------------------------------------------------- /i2c/ti/README.md: -------------------------------------------------------------------------------- 1 | [![godoc](https://img.shields.io/badge/godoc-reference-blue.svg?style=flat)](https://godoc.org/github.com/AdvancedClimateSystems/io/i2c/ti) 2 | 3 | # Texas Instruments 4 | 5 | Package ti implements drivers for I2C controlled IC's 6 | produced by [Texas Instruments](http://www.ti.com). 7 | 8 | Drivers for the following IC's are implemented: 9 | 10 | * [ADS1100](http://www.ti.com/lit/ds/symlink/ads1100.pdf) 11 | * [ADS1110](http://www.ti.com/lit/ds/symlink/ads1110.pdf) 12 | * [DAC5578](http://www.ti.com/product/dac5578) 13 | * [DAC6578](http://www.ti.com/product/dac6578) 14 | * [DAC7578](http://www.ti.com/product/dac7578) 15 | 16 | Sample usage: 17 | 18 | 19 | ```go 20 | package main 21 | 22 | import ( 23 | "fmt" 24 | 25 | "github.com/advancedclimatesystems/io/i2c/ti" 26 | "golang.org/x/exp/io/i2c" 27 | ) 28 | 29 | func main() { 30 | // We are going to write 5.5 volt to channel 0. 31 | volts := 5.5 32 | channel := 0 33 | 34 | dev, err := i2c.Open(&i2c.Devfs{ 35 | Dev: "/dev/i2c-0", 36 | }, 0x48) 37 | 38 | if err != nil { 39 | panic(fmt.Sprintf("failed to open device: %v", err)) 40 | } 41 | defer dev.Close() 42 | 43 | // Create the DAC. The reference voltage is set to 10V. 44 | dac := ti.NewDAC5578(dev, 10) 45 | 46 | // Write volts to the channel. 47 | if err = dac.SetVoltage(volts, channel); err != nil { 48 | panic(fmt.Sprintf("failed to set voltage: %v", err)) 49 | } 50 | 51 | // It's also possible to set output of a channel with digital output 52 | // code. Because the DAC5578 has a resolution of 8 bits the value must 53 | // be between 0 and 255. 54 | if err := dac.SetInputCode(255, channel); err != nil { 55 | panic(fmt.Sprintf("failed to set voltage using output code: %v", err)) 56 | } 57 | } 58 | ``` 59 | -------------------------------------------------------------------------------- /i2c/ti/ads11xx.go: -------------------------------------------------------------------------------- 1 | // Package ti contains drivers for IC's produces by Texas Instruments. 2 | package ti 3 | 4 | import ( 5 | "fmt" 6 | "math" 7 | 8 | "golang.org/x/exp/io/i2c" 9 | ) 10 | 11 | type dataRate struct { 12 | // sps is the data rate samples per second. 13 | sps int 14 | 15 | bitMask int 16 | 17 | // size is the amount of bits that represent the output code. 18 | size uint 19 | } 20 | 21 | type ads11xx struct { 22 | Conn *i2c.Device 23 | Vref float64 24 | 25 | dataRate dataRate 26 | pga int 27 | 28 | // dataRates is a map that holds all valid values for data rate. 29 | dataRates []dataRate 30 | } 31 | 32 | func newADS11xx(conn *i2c.Device, vref float64, dataRate, pga int, dataRates []dataRate) (ads11xx, error) { 33 | a := ads11xx{ 34 | Conn: conn, 35 | Vref: vref, 36 | dataRates: dataRates, 37 | } 38 | 39 | if err := a.setDataRate(dataRate); err != nil { 40 | return a, err 41 | } 42 | 43 | if err := a.SetPGA(pga); err != nil { 44 | return a, err 45 | } 46 | 47 | return a, nil 48 | } 49 | 50 | // Voltage queries the channel of an ADC and returns its voltage. 51 | func (a ads11xx) Voltage(channel int) (float64, error) { 52 | code, err := a.OutputCode(channel) 53 | if err != nil { 54 | return 0, err 55 | } 56 | 57 | max := math.Pow(2, float64(a.dataRate.size)) 58 | return ((a.Vref / max) * float64(code) / float64(a.pga)), nil 59 | } 60 | 61 | // OutputCode queries the channel and returns its digital output code. The 62 | // maximum code depends on the selected data rate. The higher the data rate, 63 | // the lower the number of bits used. 64 | func (a ads11xx) OutputCode(channel int) (int, error) { 65 | if channel != 1 { 66 | return 0, fmt.Errorf("channel %d is invalid, ADC has only 1 channel", channel) 67 | } 68 | 69 | in := make([]byte, 2) 70 | if err := a.Conn.Read(in); err != nil { 71 | return 0, fmt.Errorf("failed to read output code: %v", err) 72 | } 73 | 74 | msb := in[0] & byte(math.Pow(2, float64(a.dataRate.size-8))-1) 75 | v := (int(msb) << 8) + int(in[1]) 76 | 77 | return v, nil 78 | } 79 | 80 | // PGA reads the config register of the ADC and returns the current PGA. 81 | func (a *ads11xx) PGA() (int, error) { 82 | data, err := a.config() 83 | if err != nil { 84 | return 0, err 85 | } 86 | 87 | return int(math.Pow(2, float64(data&0x3))), nil 88 | } 89 | 90 | // SetPGA writes the value for the Programmable Gain Amplifier to the ADC. 91 | // Valid values are 1, 2, 4 and 8. 92 | func (a *ads11xx) SetPGA(v int) error { 93 | if v == 1 || v == 2 || v == 4 || v == 8 { 94 | a.pga = int(math.Log2(float64(v))) 95 | return a.setConfig() 96 | } 97 | 98 | return fmt.Errorf("PGA of %d is invalid, choose 1, 2, 4 or 8", v) 99 | } 100 | 101 | // DataRate reads the config register of the ADC returns the current value of 102 | // the data rate. 103 | func (a *ads11xx) DataRate() (int, error) { 104 | data, err := a.config() 105 | if err != nil { 106 | return 0, err 107 | } 108 | 109 | v := int(data&0xc) >> 2 110 | 111 | for _, d := range a.dataRates { 112 | if v == d.bitMask { 113 | return d.sps, nil 114 | } 115 | } 116 | 117 | return 0, fmt.Errorf("failed to understand data %x rate value read from config register", v) 118 | } 119 | 120 | // SetDataRate writes the value for the data rate to the ADC. 121 | func (a *ads11xx) SetDataRate(r int) error { 122 | if err := a.setDataRate(r); err != nil { 123 | return err 124 | } 125 | 126 | return a.setConfig() 127 | } 128 | 129 | func (a *ads11xx) setDataRate(sps int) error { 130 | var ok bool 131 | var dataRate dataRate 132 | 133 | for _, rate := range a.dataRates { 134 | if rate.sps == sps { 135 | dataRate = rate 136 | ok = true 137 | break 138 | } 139 | } 140 | 141 | if !ok { 142 | var rates []int 143 | for _, rate := range a.dataRates { 144 | rates = append(rates, rate.sps) 145 | } 146 | return fmt.Errorf("%d is an invalid value for data rate, use on of %v", sps, rates) 147 | } 148 | a.dataRate = dataRate 149 | 150 | return nil 151 | } 152 | 153 | // config reads the config register of the ADC and returns its value. 154 | func (a *ads11xx) config() (byte, error) { 155 | in := make([]byte, 3) 156 | if err := a.Conn.Read(in); err != nil { 157 | return 0, err 158 | } 159 | 160 | // The first 2 bytes contain the output code, those are ignored. The 161 | // third bytes contains value of config register. 162 | return in[2], nil 163 | } 164 | 165 | // setConfig writes the settings for the data rate and PGA to the config 166 | // register. 167 | func (a *ads11xx) setConfig() error { 168 | out := []byte{byte(a.dataRate.bitMask<<2 | a.pga)} 169 | return a.Conn.Write(out) 170 | } 171 | 172 | // ADS1100 is a 16-bit ADC. It's PGA can be set to 1, 2, 4 or 8. Allowed 173 | // values for the data rate are 8, 16, 32 or 128 SPS. 174 | type ADS1100 struct { 175 | ads11xx 176 | } 177 | 178 | // NewADS1100 returns an ADS1100. 179 | func NewADS1100(conn *i2c.Device, vref float64, rate, pga int) (*ADS1100, error) { 180 | dataRates := []dataRate{ 181 | dataRate{sps: 128, bitMask: 0x0, size: 12}, 182 | dataRate{sps: 32, bitMask: 0x1, size: 14}, 183 | dataRate{sps: 16, bitMask: 0x2, size: 15}, 184 | dataRate{sps: 8, bitMask: 0x3, size: 16}, 185 | } 186 | 187 | inner, err := newADS11xx(conn, vref, rate, pga, dataRates) 188 | if err != nil { 189 | return nil, fmt.Errorf("failed to create ADS1100: %v", err) 190 | } 191 | return &ADS1100{ 192 | inner, 193 | }, nil 194 | } 195 | 196 | // ADS1110 is a 16-bits ADC. It's PGA can be set to 1, 2, 4 or 8. Allowed 197 | // values are 15, 30, 60 or 240 SPS. The ADS1110 always uses an internal 198 | // voltage reference of 2.048V. 199 | type ADS1110 struct { 200 | ads11xx 201 | } 202 | 203 | // NewADS1110 returns an ADS1110. 204 | func NewADS1110(conn *i2c.Device, rate, pga int) (*ADS1110, error) { 205 | dataRates := []dataRate{ 206 | dataRate{sps: 240, bitMask: 0x0, size: 12}, 207 | dataRate{sps: 60, bitMask: 0x1, size: 14}, 208 | dataRate{sps: 30, bitMask: 0x2, size: 15}, 209 | dataRate{sps: 15, bitMask: 0x3, size: 16}, 210 | } 211 | 212 | inner, err := newADS11xx(conn, 2.048, rate, pga, dataRates) 213 | 214 | if err != nil { 215 | return nil, fmt.Errorf("failed to create ADS1110: %v", err) 216 | } 217 | 218 | return &ADS1110{ 219 | inner, 220 | }, nil 221 | } 222 | -------------------------------------------------------------------------------- /i2c/ti/ads11xx_test.go: -------------------------------------------------------------------------------- 1 | package ti 2 | 3 | import ( 4 | "fmt" 5 | "math" 6 | "testing" 7 | 8 | "github.com/advancedclimatesystems/io/iotest" 9 | "github.com/stretchr/testify/assert" 10 | 11 | "golang.org/x/exp/io/i2c" 12 | ) 13 | 14 | // TestADS11xxPGA tests if configuring the devices works as expected. 15 | func TestADS11xxPGA(t *testing.T) { 16 | data := make(chan []byte, 1) 17 | c := iotest.NewI2CConn() 18 | c.TxFunc(func(w, _ []byte) error { 19 | data <- w 20 | return nil 21 | }) 22 | 23 | conn, err := i2c.Open(iotest.NewI2CDriver(c), 0x1) 24 | a, err := NewADS1100(conn, 5.0, 128, 2) 25 | 26 | // Test if config register is written correctly 27 | assert.Equal(t, []byte{0x1}, <-data) 28 | 29 | // Test with invalig value for PGA. 30 | assert.NotNil(t, a.SetPGA(18)) 31 | 32 | // Test with valid value for PGA. 33 | assert.Nil(t, a.SetPGA(8)) 34 | assert.Equal(t, []byte{0x3}, <-data) 35 | 36 | c.TxFunc(func(_, r []byte) error { 37 | copy(r, []byte{0x0, 0x0, 0x3}) 38 | return nil 39 | }) 40 | 41 | pga, err := a.PGA() 42 | assert.Nil(t, err) 43 | assert.Equal(t, 8, pga) 44 | } 45 | 46 | func TestADS11xxDataRate(t *testing.T) { 47 | data := make(chan []byte, 1) 48 | c := iotest.NewI2CConn() 49 | c.TxFunc(func(w, _ []byte) error { 50 | data <- w 51 | return nil 52 | }) 53 | 54 | conn, _ := i2c.Open(iotest.NewI2CDriver(c), 0x1) 55 | a, _ := NewADS1100(conn, 5.0, 128, 2) 56 | 57 | assert.Equal(t, []byte{0x1}, <-data) 58 | 59 | assert.Nil(t, a.SetDataRate(32)) 60 | assert.Equal(t, []byte{0x5}, <-data) 61 | 62 | // Test with invalid value for data rate. 63 | assert.NotNil(t, a.SetDataRate(18)) 64 | 65 | c.TxFunc(func(_, r []byte) error { 66 | copy(r, []byte{0x0, 0x0, 0x5}) 67 | return nil 68 | }) 69 | 70 | d, err := a.DataRate() 71 | assert.Nil(t, err) 72 | assert.Equal(t, 32, d) 73 | } 74 | 75 | func TestADS1100Voltage(t *testing.T) { 76 | data := make(chan []byte, 1) 77 | c := iotest.NewI2CConn() 78 | 79 | conn, _ := i2c.Open(iotest.NewI2CDriver(c), 0x1) 80 | ads, _ := NewADS1100(conn, 5.0, 128, 2) 81 | c.TxFunc(func(w, r []byte) error { 82 | copy(r, <-data) 83 | return nil 84 | }) 85 | 86 | tests := []struct { 87 | dataRate int 88 | pga int 89 | response []byte 90 | expected float64 91 | }{ 92 | {8, 1, []byte{0xff, 0xff}, 4.99992}, 93 | {8, 2, []byte{0xff, 0xff}, 2.49996}, 94 | {8, 4, []byte{0xff, 0xff}, 1.24998}, 95 | {8, 8, []byte{0xff, 0xff}, 0.62499}, 96 | {16, 1, []byte{0xff, 0xff}, 4.99985}, 97 | {16, 2, []byte{0xff, 0xff}, 2.49992}, 98 | {32, 2, []byte{0xff, 0xff}, 2.49985}, 99 | {32, 8, []byte{0x00, 0x37}, 0.0021}, 100 | {128, 2, []byte{0xff, 0xff}, 2.49939}, 101 | } 102 | 103 | for _, test := range tests { 104 | ads.setDataRate(test.dataRate) 105 | ads.pga = test.pga 106 | 107 | data <- test.response 108 | v, _ := ads.Voltage(1) 109 | assert.Equal(t, test.expected, round(v)) 110 | } 111 | } 112 | 113 | func TestADS1110Voltage(t *testing.T) { 114 | data := make(chan []byte, 1) 115 | c := iotest.NewI2CConn() 116 | 117 | conn, _ := i2c.Open(iotest.NewI2CDriver(c), 0x1) 118 | ads, _ := NewADS1110(conn, 240, 2) 119 | c.TxFunc(func(w, r []byte) error { 120 | copy(r, <-data) 121 | return nil 122 | }) 123 | 124 | tests := []struct { 125 | dataRate int 126 | pga int 127 | response []byte 128 | expected float64 129 | }{ 130 | {15, 1, []byte{0xff, 0xff}, 2.04797}, 131 | {15, 2, []byte{0x3f, 0x9f}, 0.25448}, 132 | {30, 4, []byte{0x3f, 0x9f}, 0.25448}, 133 | {30, 8, []byte{0x00, 0xae}, 0.00136}, 134 | {60, 2, []byte{0x11, 0x2e}, 0.27487}, 135 | {60, 1, []byte{0x11, 0x2e}, 0.54975}, 136 | {240, 1, []byte{0x0b, 0x77}, 1.4675}, 137 | {240, 8, []byte{0xc0, 0x83}, 0.00819}, 138 | } 139 | 140 | for _, test := range tests { 141 | ads.setDataRate(test.dataRate) 142 | ads.pga = test.pga 143 | 144 | data <- test.response 145 | v, _ := ads.Voltage(1) 146 | assert.Equal(t, test.expected, round(v)) 147 | } 148 | } 149 | 150 | func round(f float64) float64 { 151 | shift := math.Pow(10, 5) 152 | return math.Floor((f*shift)+0.5) / shift 153 | } 154 | 155 | func ExampleADS1100() { 156 | d, err := i2c.Open(&i2c.Devfs{ 157 | Dev: "/dev/i2c-0", 158 | }, 0x1c) 159 | 160 | if err != nil { 161 | panic(fmt.Sprintf("failed to open device: %v", err)) 162 | } 163 | defer d.Close() 164 | 165 | // 4.048 is Vref, 16 is the data rate and the PGA is set to 1. 166 | adc, err := NewADS1100(d, 4.048, 16, 1) 167 | 168 | if err != nil { 169 | panic(fmt.Sprintf("failed to create ADS1100: %v", err)) 170 | } 171 | 172 | // Retrieve voltage of channel 1... 173 | v, err := adc.Voltage(1) 174 | 175 | if err != nil { 176 | panic(fmt.Sprintf("failed to read channel 1 of ADS1100: %s", err)) 177 | } 178 | 179 | // ...read the raw value of channel 1. PGA has not been applied. 180 | c, err := adc.OutputCode(1) 181 | 182 | if err != nil { 183 | panic(fmt.Sprintf("failed to read channel 1 of ADS1100: %s", err)) 184 | } 185 | 186 | fmt.Printf("channel 1 reads %f or digital output code %d", v, c) 187 | } 188 | -------------------------------------------------------------------------------- /i2c/ti/dacx578.go: -------------------------------------------------------------------------------- 1 | // Package ti contains drivers for IC's produced by Texas Instruments. 2 | package ti 3 | 4 | import ( 5 | "fmt" 6 | "math" 7 | 8 | "golang.org/x/exp/io/i2c" 9 | ) 10 | 11 | const ( 12 | // cmd is the command used to write to DAC input register channel n, 13 | // and update DAC register channel n. See table 6 of the datasheet. 14 | cmd = 0x30 15 | ) 16 | 17 | // DAC5578 is a 8 channel DAC with a resolution of 8 bits. The datasheet is 18 | // here: http://www.ti.com/lit/ds/symlink/dac5578.pdf 19 | type DAC5578 struct { 20 | dacx578 21 | } 22 | 23 | // NewDAC5578 returns a new instance of DAC5578. 24 | func NewDAC5578(conn *i2c.Device, vref float64) *DAC5578 { 25 | m := &DAC5578{ 26 | dacx578: dacx578{ 27 | conn: conn, 28 | resolution: 8, 29 | vref: vref, 30 | }, 31 | } 32 | return m 33 | } 34 | 35 | // DAC6578 is a 8 channel DAC with a resolution of 10 bits. The datasheet is 36 | // here: http://www.ti.com/lit/ds/symlink/dac6578.pdf 37 | type DAC6578 struct { 38 | dacx578 39 | } 40 | 41 | // NewDAC6578 returns a new instance of DAC5578. 42 | func NewDAC6578(conn *i2c.Device, vref float64) *DAC5578 { 43 | m := &DAC5578{ 44 | dacx578: dacx578{ 45 | conn: conn, 46 | resolution: 10, 47 | vref: vref, 48 | }, 49 | } 50 | return m 51 | } 52 | 53 | // DAC7578 is a 8 channel DAC with a resolution of 10 bits. The datasheet is 54 | // here: http://www.ti.com/lit/ds/symlink/dac7578.pdf 55 | type DAC7578 struct { 56 | dacx578 57 | } 58 | 59 | // NewDAC7578 returns a new instance of DAC5578. 60 | func NewDAC7578(conn *i2c.Device, vref float64) *DAC5578 { 61 | m := &DAC5578{ 62 | dacx578: dacx578{ 63 | conn: conn, 64 | resolution: 12, 65 | vref: vref, 66 | }, 67 | } 68 | return m 69 | } 70 | 71 | type dacx578 struct { 72 | conn *i2c.Device 73 | resolution int 74 | vref float64 75 | } 76 | 77 | // SetVoltage set output voltage of channel. Using the Vref the input code is 78 | // calculated and then SetInputCode is called. 79 | func (d *dacx578) SetVoltage(v float64, channel int) error { 80 | code := v * ((math.Pow(2, float64(d.resolution)) - 1) / d.vref) 81 | return d.SetInputCode(int(code), channel) 82 | } 83 | 84 | // SetInputCode writes the digital input code to the DAC 85 | func (d *dacx578) SetInputCode(code, channel int) error { 86 | if channel < 0 || channel > 7 { 87 | return fmt.Errorf("%d is not a valid channel", channel) 88 | } 89 | 90 | max := int(math.Pow(2, float64(d.resolution))) 91 | if code < 0 || code >= max { 92 | return fmt.Errorf("digital input code %d is out of range of 0 <= code < %d ", code, max) 93 | } 94 | 95 | // The requests is 3 bytes long. Byte 1 is the command, byte 2 and 3 96 | // contain the output code. 97 | cmdAccess := byte(cmd | channel) 98 | msb := byte((code >> uint(d.resolution-8)) & 0xFF) 99 | lsb := byte((code << uint(8-(d.resolution-8))) & 0xFF) 100 | 101 | return d.conn.Write([]byte{cmdAccess, msb, lsb}) 102 | } 103 | -------------------------------------------------------------------------------- /i2c/ti/dacx578_test.go: -------------------------------------------------------------------------------- 1 | package ti 2 | 3 | import ( 4 | "errors" 5 | "fmt" 6 | "testing" 7 | 8 | "github.com/advancedclimatesystems/io/dac" 9 | "github.com/advancedclimatesystems/io/iotest" 10 | "github.com/stretchr/testify/assert" 11 | "golang.org/x/exp/io/i2c" 12 | ) 13 | 14 | func TestDACinterface(t *testing.T) { 15 | assert.Implements(t, (*dac.DAC)(nil), new(DAC5578)) 16 | assert.Implements(t, (*dac.DAC)(nil), new(DAC6578)) 17 | assert.Implements(t, (*dac.DAC)(nil), new(DAC7578)) 18 | } 19 | 20 | func TestNewDACX578(t *testing.T) { 21 | conn, _ := i2c.Open(iotest.NewI2CDriver(iotest.NewI2CConn()), 0x1) 22 | 23 | dac5578 := NewDAC5578(conn, 3) 24 | assert.Equal(t, 8, dac5578.resolution) 25 | 26 | dac6578 := NewDAC6578(conn, 3) 27 | assert.Equal(t, 10, dac6578.resolution) 28 | 29 | dac7578 := NewDAC7578(conn, 3) 30 | assert.Equal(t, 12, dac7578.resolution) 31 | } 32 | 33 | func TestDACX578SetVoltage(t *testing.T) { 34 | data := make(chan []byte, 2) 35 | c := iotest.NewI2CConn() 36 | c.TxFunc(func(w, _ []byte) error { 37 | data <- w 38 | return nil 39 | }) 40 | 41 | conn, _ := i2c.Open(iotest.NewI2CDriver(c), 0x1) 42 | m := dacx578{ 43 | conn: conn, 44 | } 45 | 46 | var tests = []struct { 47 | resolution int 48 | vref float64 49 | voltage float64 50 | channel int 51 | expected []byte 52 | }{ 53 | {8, 10, 10, 1, []byte{0x31, 0xff, 0}}, 54 | {8, 10, 5, 1, []byte{0x31, 0x7f, 0}}, 55 | {8, 10, 0, 2, []byte{0x32, 0x0, 0}}, 56 | {8, 5, 5, 2, []byte{0x32, 0xff, 0}}, 57 | {8, 20, 10, 2, []byte{0x32, 0x7f, 0}}, 58 | {10, 10, 10, 2, []byte{0x32, 0xff, 0xc0}}, 59 | {10, 10, 5, 2, []byte{0x32, 0x7f, 0xc0}}, 60 | {10, 10, 0, 2, []byte{0x32, 0x00, 0x00}}, 61 | {12, 10, 10, 3, []byte{0x33, 0xff, 0xf0}}, 62 | {12, 10, 5, 3, []byte{0x33, 0x7f, 0xf0}}, 63 | {12, 10, 0, 4, []byte{0x34, 0x00, 0x00}}, 64 | } 65 | 66 | for _, test := range tests { 67 | m.resolution = test.resolution 68 | m.vref = test.vref 69 | 70 | err := m.SetVoltage(test.voltage, test.channel) 71 | if err != nil { 72 | t.Fatal(err) 73 | } 74 | 75 | assert.Equal(t, test.expected, <-data) 76 | } 77 | } 78 | 79 | func TestDACX578SetVoltageChannelOutOfRange(t *testing.T) { 80 | c := iotest.NewI2CConn() 81 | c.TxFunc(func(w, _ []byte) error { 82 | return nil 83 | }) 84 | 85 | conn, _ := i2c.Open(iotest.NewI2CDriver(c), 0x1) 86 | m := dacx578{ 87 | conn: conn, 88 | resolution: 10, 89 | vref: 10, 90 | } 91 | 92 | var tests = []struct { 93 | channel int 94 | expected error 95 | }{ 96 | {0, nil}, 97 | {7, nil}, 98 | {8, errors.New("8 is not a valid channel")}, 99 | {-1, errors.New("-1 is not a valid channel")}, 100 | } 101 | 102 | for _, test := range tests { 103 | err := m.SetVoltage(5, test.channel) 104 | assert.Equal(t, test.expected, err) 105 | } 106 | } 107 | 108 | func TestDACX578SetVoltageOutRange(t *testing.T) { 109 | c := iotest.NewI2CConn() 110 | c.TxFunc(func(w, _ []byte) error { 111 | return nil 112 | }) 113 | 114 | conn, _ := i2c.Open(iotest.NewI2CDriver(c), 0x1) 115 | m := dacx578{ 116 | conn: conn, 117 | } 118 | 119 | var tests = []struct { 120 | vref float64 121 | voltage float64 122 | resolution int 123 | expected error 124 | }{ 125 | {10, 10, 8, nil}, 126 | {10, 10, 10, nil}, 127 | {10, 10, 12, nil}, 128 | {10, 11, 8, errors.New("digital input code 280 is out of range of 0 <= code < 256 ")}, 129 | {10, 11, 10, errors.New("digital input code 1125 is out of range of 0 <= code < 1024 ")}, 130 | {10, 11, 12, errors.New("digital input code 4504 is out of range of 0 <= code < 4096 ")}, 131 | } 132 | 133 | for _, test := range tests { 134 | m.resolution = test.resolution 135 | m.vref = test.vref 136 | 137 | err := m.SetVoltage(test.voltage, 1) 138 | assert.Equal(t, test.expected, err) 139 | } 140 | } 141 | 142 | func ExampleDAC5578() { 143 | // We are going to write 5.5 volt to channel 0. 144 | volts := 5.5 145 | channel := 0 146 | 147 | dev, err := i2c.Open(&i2c.Devfs{ 148 | Dev: "/dev/i2c-0", 149 | }, 0x48) 150 | 151 | if err != nil { 152 | panic(fmt.Sprintf("failed to open device: %v", err)) 153 | } 154 | defer dev.Close() 155 | 156 | // Create the DAC. The reference voltage is set to 10V. 157 | dac := NewDAC5578(dev, 10) 158 | 159 | // Write volts to the channel. 160 | err = dac.SetVoltage(volts, channel) 161 | if err != nil { 162 | panic(fmt.Sprintf("failed to set voltage: %v", err)) 163 | } 164 | 165 | // It's also possible to set output of a channel with digital output 166 | // code. The value must be between 0 and 255. 167 | if err := dac.SetInputCode(255, channel); err != nil { 168 | panic(fmt.Sprintf("failed to set voltage using output code: %v", err)) 169 | } 170 | } 171 | -------------------------------------------------------------------------------- /iotest/spi.go: -------------------------------------------------------------------------------- 1 | // Package iotest contains some test helpers for code relying on 2 | // golang.org/x/exp/io/i2c. 3 | // 4 | // func TestMCP4725(t *testing.T) { 5 | // data := make(chan []byte, 2) 6 | // c := iotest.NewI2CConn() 7 | // 8 | // // Set the TxFunc. 9 | // c.TxFunc(func(w, _ []byte) error { 10 | // data <- w 11 | // return nil 12 | // }) 13 | // 14 | // conn, _ := i2c.Open(iotest.NewI2CDriver(c), 0x1) 15 | // dac, _ := microchip.NewMCP4725(conn, 5.5) 16 | // 17 | // // Under the hood SetInputCode calls c.Tx which in turn calls TxFunc defined earlier. 18 | // dac.SetInputCode(0x539, 1) 19 | // 20 | // assert.Equal(t, []byte{0x5, 0x39}, <-data) 21 | // } 22 | package iotest 23 | 24 | import ( 25 | "golang.org/x/exp/io/i2c/driver" 26 | ) 27 | 28 | // I2CDriver implements the i2c.Device interface. 29 | type I2CDriver struct { 30 | conn I2CConn 31 | } 32 | 33 | // NewI2CDriver creates a new I2CDriver. 34 | func NewI2CDriver(c I2CConn) *I2CDriver { 35 | return &I2CDriver{conn: c} 36 | } 37 | 38 | // Open returns a type that implements the driver.Conn interface. 39 | func (d I2CDriver) Open(_ int, _ bool) (driver.Conn, error) { 40 | return d.conn, nil 41 | } 42 | 43 | // I2CConn implements the driver.Conn interface. 44 | type I2CConn struct { 45 | tx *tx 46 | close *close 47 | } 48 | 49 | type tx struct { 50 | f func(w, r []byte) error 51 | } 52 | 53 | type close struct { 54 | f func() error 55 | } 56 | 57 | // NewI2CConn creates a new I2CConn. 58 | func NewI2CConn() I2CConn { 59 | c := I2CConn{ 60 | tx: &tx{}, 61 | close: &close{}, 62 | } 63 | 64 | c.TxFunc(func(_, _ []byte) error { 65 | return nil 66 | }) 67 | 68 | c.CloseFunc(func() error { 69 | return nil 70 | }) 71 | 72 | return c 73 | } 74 | 75 | // Tx calls the TxFunc. 76 | func (c I2CConn) Tx(w, r []byte) error { 77 | return c.tx.f(w, r) 78 | } 79 | 80 | // TxFunc sets TxFunc which is called when Tx is called. 81 | func (c *I2CConn) TxFunc(f func(w, r []byte) error) { 82 | c.tx.f = f 83 | } 84 | 85 | // Close calls the CloseFunc. 86 | func (c I2CConn) Close() error { 87 | return c.close.f() 88 | } 89 | 90 | // CloseFunc sets the CloseFunc. CloseFunc is called when Close is called. 91 | func (c *I2CConn) CloseFunc(f func() error) { 92 | c.close.f = f 93 | } 94 | -------------------------------------------------------------------------------- /spi/microchip/README.md: -------------------------------------------------------------------------------- 1 | [![godoc](https://img.shields.io/badge/godoc-reference-blue.svg?style=flat)](https://godoc.org/github.com/AdvancedClimateSystems/io/spi/microchip) 2 | 3 | # Microchip 4 | 5 | Package microchip implements drivers for a few SPI controlled IC's produced by 6 | [Microchip](http://www.microchip.com/). This package relies on 7 | [x/exp/io/spi](https://godoc.org/golang.org/x/exp/io/spi). 8 | 9 | MCP3x0x is a family of Analog Digital Converters (ADC). 10 | Currently the package contains drivers for the following ADC: 11 | 12 | * [MCP3004](http://www.microchip.com/wwwproducts/en/MCP3004) 13 | * [MCP3008](http://www.microchip.com/wwwproducts/en/MCP3008) 14 | * [MCP3204](http://www.microchip.com/wwwproducts/en/MCP3204) 15 | * [MCP3208](http://www.microchip.com/wwwproducts/en/MCP3208) 16 | 17 | Sample usage: 18 | 19 | ``` go 20 | package main 21 | 22 | import ( 23 | "fmt" 24 | 25 | "golang.org/x/exp/io/spi" 26 | "github.com/advancedclimatesystems/io/spi/microchip" 27 | ) 28 | 29 | func main() { 30 | conn, err := spi.Open(&spi.Devfs{ 31 | Dev: "/dev/spidev32766.0", 32 | Mode: spi.Mode0, 33 | MaxSpeed: 3600000, 34 | }) 35 | 36 | if err != nil { 37 | panic(fmt.Sprintf("failed to open SPI device: %s", err)) 38 | } 39 | 40 | defer conn.Close() 41 | 42 | adc := microchip.MCP3008{ 43 | Conn: conn, 44 | Vref: 5.0, 45 | } 46 | 47 | // Read the voltage of channel 3... 48 | v, err := adc.Voltage(3) 49 | 50 | if err != nil { 51 | panic(fmt.Sprintf("failed to read channel 3 of MCP3008: %s", err)) 52 | } 53 | 54 | // ...or read the raw value of channel 3. 55 | c, err := adc.OutputCode(3) 56 | 57 | fmt.Printf("channel 3 reads %f Volts or digital output code %d", v, c) 58 | } 59 | ``` 60 | -------------------------------------------------------------------------------- /spi/microchip/mcp3x0x.go: -------------------------------------------------------------------------------- 1 | // Package microchip implements drivers for a few SPI controlled chips produced 2 | // by Microchip. 3 | package microchip 4 | 5 | import ( 6 | "fmt" 7 | 8 | "github.com/advancedclimatesystems/io/adc" 9 | "golang.org/x/exp/io/spi" 10 | ) 11 | 12 | // MCP3004 is 10-bits ADC with 4 single-ended or 2 pseudo-differential inputs. 13 | // Datasheet: http://ww1.microchip.com/downloads/en/DeviceDoc/21295C.pdf 14 | type MCP3004 struct { 15 | Conn *spi.Device 16 | 17 | // Vref is the voltage on the reference input of the ADC. 18 | Vref float64 19 | 20 | InputType adc.InputType 21 | } 22 | 23 | // OutputCode queries the channel and returns its digital output code. 24 | func (m MCP3004) OutputCode(channel int) (int, error) { 25 | if channel < 0 || channel > 3 { 26 | return 0, fmt.Errorf("channel %d is invalid, ADC has only 4 channels", channel) 27 | } 28 | 29 | code, err := read10(m.Conn, channel, m.InputType) 30 | if err != nil { 31 | return 0, err 32 | } 33 | 34 | return code, nil 35 | } 36 | 37 | // Voltage returns the voltage of a channel. 38 | func (m MCP3004) Voltage(channel int) (float64, error) { 39 | code, err := m.OutputCode(channel) 40 | if err != nil { 41 | return 0, err 42 | } 43 | 44 | return (m.Vref / 1024) * float64(code), nil 45 | } 46 | 47 | // MCP3008 is 10-bits ADC with 8 single-ended or 4 pseudo-differential inputs. 48 | // Datasheet: http://ww1.microchip.com/downloads/en/DeviceDoc/21295C.pdf 49 | type MCP3008 struct { 50 | Conn *spi.Device 51 | 52 | // Vref is the voltage on the reference input of the ADC. 53 | Vref float64 54 | 55 | InputType adc.InputType 56 | } 57 | 58 | // OutputCode queries the channel and returns its digital output code. 59 | func (m MCP3008) OutputCode(channel int) (int, error) { 60 | if channel < 0 || channel > 7 { 61 | return 0, fmt.Errorf("channel %d is invalid, ADC has only 7 channels", channel) 62 | } 63 | 64 | code, err := read10(m.Conn, channel, m.InputType) 65 | if err != nil { 66 | return 0, err 67 | } 68 | 69 | return code, nil 70 | } 71 | 72 | // Voltage returns the voltage of a channel. 73 | func (m MCP3008) Voltage(channel int) (float64, error) { 74 | code, err := m.OutputCode(channel) 75 | if err != nil { 76 | return 0, err 77 | } 78 | 79 | return (m.Vref / 1024) * float64(code), nil 80 | } 81 | 82 | // read10 reads a 10 bits value from an channel of an ADC. 83 | func read10(conn *spi.Device, channel int, inputType adc.InputType) (int, error) { 84 | var cmd int 85 | 86 | // The first bit after the start bit will determine if the conversion 87 | // is done using single-ended or differential input mode. 0 means 88 | // differential, 1 means single-ended. 89 | if inputType == adc.SingleEnded { 90 | cmd = 1 91 | } 92 | // The bit is then shifted 3 times and the number is incremented with 93 | // a 3 bits channel. 94 | cmd = cmd << 3 95 | cmd += channel 96 | 97 | // The result is shifted 4 times so the high nibble of the byte 98 | // contains 4 bits of data. 99 | // 100 | // 1 1 1 1 x x x x 101 | // | | | | ------- 4 empty bits. 102 | // | ------------- 3 bits selecting a channel 103 | // --------------- The bit defining single-ended or pseudo-differential input mode. 104 | cmd = cmd << 4 105 | 106 | // The first byte contains a start bit, the second byte contains the 107 | // actual data and the third byte is another empty byte. 108 | out := []byte{1, byte(cmd), 0} 109 | 110 | // For every byte send the SPI master reads a byte. Because we send 3 111 | // bytes we read 3 bytes. 112 | in := make([]byte, 3) 113 | 114 | if err := conn.Tx(out, in); err != nil { 115 | return 0, fmt.Errorf("failed to read channel %d: %v", channel, err) 116 | } 117 | 118 | // The 10-bits measurement are at the end of the 3 byte response. 119 | // 120 | // 11111111 11111010 10110111 121 | // ^^ ^^^^^^^^ 122 | // To get the base10 value of the channel the second byte is masked 123 | // with 3: 124 | // 125 | // 11111010 126 | // 00000011 127 | // -------- & 128 | // 00000010 129 | // 130 | // The byte is shifted 8 bits and the last byte is added: 131 | // 00000010 00000000 132 | // 10110111 133 | // -------- + 134 | // 00000010 10110111 135 | // 136 | // 00000010 10110111 is 696 in base10. 137 | return int(in[1]&3)<<8 + int(in[2]), nil 138 | } 139 | 140 | // MCP3204 is 12-bits ADC with 4 single-ended or 2 pseudo-differential inputs. 141 | // Datasheet: http://ww1.microchip.com/downloads/en/DeviceDoc/21298e.pdf 142 | type MCP3204 struct { 143 | Conn *spi.Device 144 | 145 | // Vref is the voltage on the reference input of the ADC. 146 | Vref float64 147 | 148 | InputType adc.InputType 149 | } 150 | 151 | // OutputCode queries the channel and returns its digital output code. 152 | func (m MCP3204) OutputCode(channel int) (int, error) { 153 | if channel < 0 || channel > 3 { 154 | return 0, fmt.Errorf("channel %d is invalid, ADC has only 4 channels", channel) 155 | } 156 | 157 | code, err := read12(m.Conn, channel, m.InputType) 158 | if err != nil { 159 | return 0, err 160 | } 161 | 162 | return code, nil 163 | } 164 | 165 | // Voltage returns the voltage of a channel. 166 | func (m MCP3204) Voltage(channel int) (float64, error) { 167 | code, err := m.OutputCode(channel) 168 | if err != nil { 169 | return 0, err 170 | } 171 | 172 | return (m.Vref / 4096) * float64(code), nil 173 | } 174 | 175 | // MCP3208 is 12-bits ADC with 8 single-ended or 4 pseudo-differential inputs. 176 | // Datasheet: http://ww1.microchip.com/downloads/en/DeviceDoc/21298e.pdf 177 | type MCP3208 struct { 178 | Conn *spi.Device 179 | 180 | // Vref is the voltage on the reference input of the ADC. 181 | Vref float64 182 | 183 | InputType adc.InputType 184 | } 185 | 186 | // OutputCode queries the channel and returns its digital output code. 187 | func (m MCP3208) OutputCode(channel int) (int, error) { 188 | if channel < 0 || channel > 7 { 189 | return 0, fmt.Errorf("channel %d is invalid, ADC has only 7 channels", channel) 190 | } 191 | 192 | code, err := read12(m.Conn, channel, m.InputType) 193 | if err != nil { 194 | return 0, err 195 | } 196 | 197 | return code, nil 198 | } 199 | 200 | // Voltage returns the voltage of a channel. 201 | func (m MCP3208) Voltage(channel int) (float64, error) { 202 | code, err := m.OutputCode(channel) 203 | if err != nil { 204 | return 0, err 205 | } 206 | 207 | return (m.Vref / 4096) * float64(code), nil 208 | } 209 | 210 | // read12 reads a 12 bits value from an channel of an ADC. 211 | func read12(conn *spi.Device, channel int, inputType adc.InputType) (int, error) { 212 | // The start bit. 213 | cmd := 1 214 | cmd = cmd << 1 215 | 216 | // The first bit after the start bit will determine if the conversion 217 | // is done using single-ended or differential input mode. 0 means 218 | // differential, 1 means single-ended. 219 | if inputType == adc.SingleEnded { 220 | cmd = cmd | 1 221 | } 222 | // The bit is then shifted 3 times and the number is incremented with 223 | // a 3 bits channel. 224 | cmd = cmd << 3 225 | cmd += channel 226 | 227 | // The result is shifted 6 times. 228 | // 229 | // x x x x x 1 1 1 1 1 x x x x x x 230 | // | | | | | ------- 3 bits for selecting channel 231 | // | |---------------- 1 bit defining single-ended or pseudo-differential input mode 232 | // |------------------ 1 start bit 233 | cmd = cmd << 6 234 | 235 | // The data is is in the first 2 bytes, the third byte is an empty byte. 236 | out := []byte{byte(cmd >> 8), byte(cmd & 0xFF), 0} 237 | 238 | // For every byte send the SPI master reads a byte. Because we send 3 239 | // bytes we read 3 bytes. 240 | in := make([]byte, 3) 241 | 242 | if err := conn.Tx(out, in); err != nil { 243 | return 0, fmt.Errorf("failed to read channel %d: %v", channel, err) 244 | } 245 | 246 | // The 12-bits measurement is at the end of the 3 byte response. 247 | // 248 | // 11111111 11101100 10110111 249 | // ^^^^ ^^^^^^^^ 250 | // To get the base10 value of the channel the second byte is masked 251 | // with 15: 252 | // 253 | // 11101100 254 | // 00001111 255 | // -------- & 256 | // 00001100 257 | // 258 | // The byte is shifted 8 bits and the last byte is added: 259 | // 00001100 00000000 260 | // 10110111 261 | // -------- + 262 | // 00001100 10110111 263 | // 264 | // 00001100 10110111 is 3255 in base10. 265 | return int(in[1]&0xF)<<8 + int(in[2]), nil 266 | } 267 | -------------------------------------------------------------------------------- /spi/microchip/mcp3x0x_test.go: -------------------------------------------------------------------------------- 1 | package microchip 2 | 3 | import ( 4 | "fmt" 5 | "testing" 6 | 7 | "github.com/advancedclimatesystems/io/adc" 8 | "github.com/stretchr/testify/assert" 9 | "golang.org/x/exp/io/spi" 10 | "golang.org/x/exp/io/spi/driver" 11 | ) 12 | 13 | // testDriver is a mocked driver that implements the driver.Opener interface. 14 | type testDriver struct { 15 | conn testConn 16 | } 17 | 18 | func (d testDriver) Open() (driver.Conn, error) { 19 | return d.conn, nil 20 | } 21 | 22 | // testConn is a mocked connection that implements the spi.Conn interface. 23 | type testConn struct { 24 | tx func(w, r []byte) error 25 | } 26 | 27 | func (c testConn) Configure(k, v int) error { return nil } 28 | 29 | func (c testConn) Tx(w, r []byte) error { 30 | return c.tx(w, r) 31 | } 32 | 33 | func (c testConn) Close() error { return nil } 34 | 35 | func TestMCP300x(t *testing.T) { 36 | var tests = []struct { 37 | resp []byte 38 | v float64 39 | }{ 40 | {[]byte{0, 0}, 0}, 41 | {[]byte{2, 0}, 2.5}, 42 | {[]byte{6, 0}, 2.5}, 43 | {[]byte{255, 255}, 4.9951171875}, 44 | } 45 | 46 | for _, test := range tests { 47 | c := testConn{ 48 | tx: func(w, r []byte) error { 49 | assert.Equal(t, []byte{1, 176, 0}, w) 50 | 51 | r[1] = test.resp[0] 52 | r[2] = test.resp[1] 53 | 54 | return nil 55 | }, 56 | } 57 | 58 | con, _ := spi.Open(&testDriver{c}) 59 | mcp3004 := MCP3004{ 60 | Conn: con, 61 | Vref: 5.0, 62 | InputType: adc.SingleEnded, 63 | } 64 | 65 | v, _ := mcp3004.Voltage(3) 66 | assert.Equal(t, test.v, v) 67 | 68 | mcp3008 := MCP3008{ 69 | Conn: con, 70 | Vref: 5.0, 71 | InputType: adc.SingleEnded, 72 | } 73 | 74 | v, _ = mcp3008.Voltage(3) 75 | assert.Equal(t, test.v, v) 76 | } 77 | } 78 | 79 | func TestMCP320x(t *testing.T) { 80 | var tests = []struct { 81 | resp []byte 82 | v float64 83 | }{ 84 | {[]byte{0, 0}, 0}, 85 | {[]byte{2, 0}, 0.625}, 86 | {[]byte{6, 0}, 1.875}, 87 | {[]byte{1, 13}, 0.328369140625}, 88 | {[]byte{255, 255}, 4.998779296875}, 89 | } 90 | 91 | for _, test := range tests { 92 | c := testConn{ 93 | tx: func(w, r []byte) error { 94 | assert.Equal(t, []byte{4, 192, 0}, w) 95 | 96 | r[1] = test.resp[0] 97 | r[2] = test.resp[1] 98 | 99 | return nil 100 | }, 101 | } 102 | 103 | con, _ := spi.Open(&testDriver{c}) 104 | mcp3204 := MCP3204{ 105 | Conn: con, 106 | Vref: 5.0, 107 | InputType: adc.PseudoDifferential, 108 | } 109 | 110 | v, _ := mcp3204.Voltage(3) 111 | assert.Equal(t, test.v, v) 112 | 113 | mcp3208 := MCP3208{ 114 | Conn: con, 115 | Vref: 5.0, 116 | InputType: adc.PseudoDifferential, 117 | } 118 | 119 | v, _ = mcp3208.Voltage(3) 120 | assert.Equal(t, test.v, v) 121 | 122 | } 123 | } 124 | 125 | // TestWithInvalidChannels calls adc.OutputCode with a channel that isn't in 126 | // the range of the ADC. 127 | func TestMCP3x0xWithInvalidChannels(t *testing.T) { 128 | var tests = []struct { 129 | adc adc.ADC 130 | channel int 131 | }{ 132 | {MCP3004{}, -1}, 133 | {MCP3004{}, 4}, 134 | {MCP3008{}, -1}, 135 | {MCP3008{}, 8}, 136 | {MCP3204{}, -1}, 137 | {MCP3204{}, 4}, 138 | {MCP3208{}, -1}, 139 | {MCP3208{}, 8}, 140 | } 141 | 142 | for _, test := range tests { 143 | _, err := test.adc.OutputCode(test.channel) 144 | assert.NotNil(t, err) 145 | } 146 | } 147 | 148 | // TestMCP3x0xWithFailingConnection test if all ADC's return errors when the 149 | // connection fails. 150 | func TestMCP3x0xWithFailingConnection(t *testing.T) { 151 | c := testConn{ 152 | tx: func(w, r []byte) error { 153 | return fmt.Errorf("some error occured") 154 | }, 155 | } 156 | con, _ := spi.Open(&testDriver{c}) 157 | 158 | adcs := []adc.ADC{ 159 | MCP3004{ 160 | Conn: con, 161 | }, 162 | MCP3008{ 163 | Conn: con, 164 | }, 165 | MCP3204{ 166 | Conn: con, 167 | }, 168 | MCP3208{ 169 | Conn: con, 170 | }, 171 | } 172 | 173 | for _, adc := range adcs { 174 | _, err := adc.Voltage(3) 175 | assert.NotNil(t, err) 176 | } 177 | } 178 | 179 | func TestRead12(t *testing.T) { 180 | tests := []struct { 181 | channel int 182 | inputType adc.InputType 183 | expectedCmd []byte 184 | }{ 185 | {1, adc.PseudoDifferential, []byte{4, 64, 0}}, 186 | {1, adc.SingleEnded, []byte{6, 64, 0}}, 187 | {3, adc.PseudoDifferential, []byte{4, 192, 0}}, 188 | {3, adc.SingleEnded, []byte{6, 192, 0}}, 189 | } 190 | 191 | for _, test := range tests { 192 | c := testConn{ 193 | tx: func(w, r []byte) error { 194 | assert.Equal(t, test.expectedCmd, w) 195 | return nil 196 | }, 197 | } 198 | 199 | con, _ := spi.Open(&testDriver{c}) 200 | 201 | _, _ = read12(con, test.channel, test.inputType) 202 | } 203 | } 204 | 205 | func ExampleMCP3008() { 206 | conn, err := spi.Open(&spi.Devfs{ 207 | Dev: "/dev/spidev32766.0", 208 | Mode: spi.Mode0, 209 | MaxSpeed: 3600000, 210 | }) 211 | 212 | if err != nil { 213 | panic(fmt.Sprintf("failed to open SPI device: %s", err)) 214 | } 215 | 216 | defer conn.Close() 217 | 218 | a := MCP3008{ 219 | Conn: conn, 220 | Vref: 5.0, 221 | 222 | // Optional, default value is SingleEnded. 223 | InputType: adc.SingleEnded, 224 | } 225 | 226 | // Voltage the voltage on channel 3. 227 | v, err := a.Voltage(3) 228 | if err != nil { 229 | panic(fmt.Sprintf("failed to read channel 3 of MCP3008: %s", err)) 230 | } 231 | 232 | fmt.Printf("read %f Volts from channel 3", v) 233 | } 234 | --------------------------------------------------------------------------------