├── .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 | [](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 | [](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 | [](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 | [](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 | [](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 | [](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 |
--------------------------------------------------------------------------------