forked from parallaxinc/spin-standard-library
-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathsensor.power.ina219.spin
379 lines (328 loc) · 11.5 KB
/
sensor.power.ina219.spin
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
{
--------------------------------------------
Filename: sensor.power.ina219.spin
Author: Jesse Burt
Description: Driver of the TI INA219 current/power monitor IC
Copyright (c) 2022
Started Sep 18, 2019
Updated Dec 31, 2023
See end of file for terms of use.
--------------------------------------------
}
#include "sensor.power.common.spinh"
CON
SLAVE_WR = core#SLAVE_ADDR
SLAVE_RD = core#SLAVE_ADDR|1
DEF_SCL = 28
DEF_SDA = 29
DEF_HZ = 100_000
DEF_ADDR = %0000
I2C_MAX_FREQ = core#I2C_MAX_FREQ
' Address pins vs slave addresses
' A1 A0 SLAVE ADDRESS
' GND GND 100_0000
' GND VS+ 100_0001
' GND SDA 100_0010
' GND SCL 100_0011
' VS+ GND 100_0100
' VS+ VS+ 100_0101
' VS+ SDA 100_0110
' VS+ SCL 100_0111
' SDA GND 100_1000
' SDA VS+ 100_1001
' SDA SDA 100_1010
' SDA SCL 100_1011
' SCL GND 100_1100
' SCL VS+ 100_1101
' SCL SDA 100_1110
' SCL SCL 100_1111
' Operating modes
SLEEP = 0
SHUNTV_SNGL = 1
BUSV_SNGL = 2
BOTH_SNGL = 3
STANDBY = 4
SHUNTV_CONT = 5
BUSV_CONT = 6
BOTH_CONT = 7
{ default I/O settings; these can be overridden in the parent object }
SCL = DEF_SCL
SDA = DEF_SDA
I2C_FREQ = DEF_HZ
I2C_ADDR = DEF_ADDR
OBJ
#ifdef INA219_I2C_BC
i2c: "com.i2c.nocog"
#else
i2c: "com.i2c"
#endif
core: "core.con.ina219"
time: "time"
VAR
long _shunt_res
long _i_max
long _i_lsb, _p_lsb
long _vmax_shunt
long _addr_bits
PUB null{}
' This is not a top-level object
PUB start(): status
' Start using default I/O settings
return startx(SCL, SDA, I2C_FREQ, I2C_ADDR)
PUB startx(SCL_PIN, SDA_PIN, I2C_HZ, ADDR_BITS): status
' Start using custom settings
' validate I/O pins, bus speed and I2C address option bits
if ( lookdown(SCL_PIN: 0..31) and lookdown(SDA_PIN: 0..31) and ...
lookdown(ADDR_BITS: %0000..%1111) )
if ( status := i2c.init(SCL_PIN, SDA_PIN, I2C_HZ) )
time.msleep(1)
_addr_bits := ADDR_BITS << 1
' test device bus presence
if ( i2c.present(SLAVE_WR | _addr_bits) )
if ( dev_id{} == core#DEVID_RESP )
return
' if this point is reached, something above failed
' Double check I/O pin assignments, connections, power
' Lastly - make sure you have at least one free core/cog
return FALSE
PUB stop{}
' Stop the driver
i2c.deinit{}
PUB defaults{}
' Factory default settings
' POR settings:
' bus_voltage_rng(32)
' shunt_voltage_rng(320)
' bus_adc_res(12)
' shunt_adc_res(12)
' opmode(BOTH_CONT)
reset{}
PUB preset_320s_2a_100mohm{}
' Preset: 'XXX for coming up with a value for current_scale()
' 32V bus voltage range
' 320mV shunt voltage range
' 12bit shunt ADC res
' 2A maximum current
' 100mOhm shunt resistor
shunt_resistance(100)
_i_max := 2 * 1_000
_i_lsb := _i_max / 32768
_p_lsb := _i_lsb * 20
_vmax_shunt := _i_max * _shunt_res
bus_voltage_rng(32)
shunt_voltage_rng(320)
shunt_adc_res(12)
shunt_samples(1)
bus_adc_res(12)
PUB adc2amps(adc_word): a
' Convert current ADC word to amperage
return (~~adc_word) * 1_00
PUB adc2shunt_volts(adc_word): v
' Convert shunt voltage ADC word to voltage
return (~~adc_word * 10)
PUB adc2volts(adc_word): v
' Convert bus voltage ADC word to voltage
{ discard 3 LSBs (not part of the measurement), but preserve the sign }
return ((adc_word ~> 3) * 4_000)
PUB adc2watts(adc_word): w
' Convert power ADC word to wattage
return (adc_word * 20_00)
PUB bus_adc_res(adcres): curr_res
' Set bus ADC resolution, in bits
' Valid values: 9, 10, 11, *12
' Any other value polls the chip and returns the current setting
curr_res := 0
readreg(core#CONFIG, 2, @curr_res)
case adcres
9..12:
adcres := (adcres-9) << core#BADC
other:
curr_res := (curr_res >> core#BADC) & core#BADC_BITS
return (curr_res + 9)
adcres := ((curr_res & core#BADC_MASK) | adcres)
writereg(core#CONFIG, 2, @adcres)
PUB bus_voltage_rng(range): curr_rng
' Set bus voltage range
' Valid values: 16, *32
' Any other value polls the chip and returns the current setting
curr_rng := 0
readreg(core#CONFIG, 2, @curr_rng)
case range
16, 32:
range := ((range / 16)-1) << core#BRNG
other:
curr_rng := (curr_rng >> core#BRNG) & 1
return lookupz(curr_rng: 16, 32)
range := ((curr_rng & core#BRNG_MASK) | range)
writereg(core#CONFIG, 2, @range)
PUB current_data{}: a
' Read current
' Returns: Current in milliamps
readreg(core#CURRENT, 2, @a)
PUB current_scale{}: scale
' Get current scale
' Returns: current scale, in LSBs
scale := 0
readreg(core#CALIBRATION, 2, @scale)
PUB current_set_scale(scale)
' Set current scale, in LSBs
' Valid values: *0..65534 (even numbers only)
' Any other value polls the chip and returns the current setting
' NOTE: Current and power readings will always be 0,
' unless this value is set non-zero
scale := 0 #> (scale & core#CALIBRATION_MASK) <# 65534
writereg(core#CALIBRATION, 2, @scale)
PUB dev_id{}: id
' Read device ID
' Returns: POR value of the configuration register
' NOTE: This method performs a soft-reset of the chip and reads the value of
' the configuration register, thus it isn't an ID, per se
id := 0
reset{}
readreg(core#CONFIG, 2, @id)
PUB opmode(mode): curr_mode
' Set device operating mode
' Valid values:
' SLEEP (0): Power-down
' SHUNTV_SNGL (1): Shunt voltage measurement, single
' BUSV_SNGL (2): Bus voltage measurement, single
' BOTH_SNGL (3): Shunt and vus voltage measurement, single
' STANDBY (4): Disable ADC
' SHUNTV_CONT (5): Shunt voltage measurements, continuous
' BUSV_CONT (6): Bus voltage measurements, continuous
' BOTH_CONT (7): Shunt and bus voltage measurements, continuous
' Any other value polls the chip and returns the current setting
curr_mode := 0
readreg(core#CONFIG, 1, @curr_mode)
case mode
SLEEP, SHUNTV_SNGL, BUSV_SNGL, BOTH_SNGL, STANDBY, SHUNTV_CONT, BUSV_CONT, BOTH_CONT:
other:
return curr_mode & core#MODE_BITS
mode := ((curr_mode & core#MODE_MASK) | mode)
writereg(core#CONFIG, 1, @mode)
PUB power_data{}: pwr_adc
' Read power ADC data
' Returns: s16
pwr_adc := 0
readreg(core#POWER, 2, @pwr_adc)
PUB reset{} | tmp
' Perform a soft-reset of the chip
tmp := (1 << core#RST)
writereg(core#CONFIG, 2, @tmp)
PUB shunt_adc_res(adc_res): curr_res
' Set shunt ADC resolution, in bits
' Valid values: 9, 10, 11, *12
' Any other value polls the chip and returns the current setting
' NOTE: This setting and shunt_samples() are mutually exclusive. If both
' methods are called, the most recent will be the setting used.
curr_res := 0
readreg(core#CONFIG, 2, @curr_res)
case adc_res
9..12:
adc_res := (adc_res - 9) << core#SADC
other:
curr_res := (curr_res >> core#SADC) & core#SADC_BITS
return (curr_res + 9)
adc_res := ((curr_res & core#SADC_MASK) | adc_res)
writereg(core#CONFIG, 2, @adc_res)
PUB shunt_resistance(r_shunt): curr_res
' Set value of shunt resistor, in milliohms
case r_shunt
1..1_000:
_shunt_res := r_shunt
other:
return _shunt_res
PUB shunt_samples(samples): curr_smp
' Set number of shunt ADC samples to take when averaging
' Valid values: 1, 2, 4, 8, 16, 32, 64, 128
' Any other value polls the chip and returns the current setting
' NOTE: All averaging modes are performed at 12-bit resolution
' NOTE: Conversion time is approx 532uSec * number of samples
' NOTE: 1 effectively disables averaging
' NOTE: This setting and shunt_adc_res() are mutually exclusive. If both
' methods are called, the most recent will be the setting used.
curr_smp := 0
readreg(core#CONFIG, 2, @curr_smp)
case samples
1..128:
samples := ((>| samples) - 1) << core#SADC
samples |= (1 << core#SADC_AVG)
other:
curr_smp := (curr_smp >> core#SADC) & core#SADC_BITS
if (curr_smp & %1000) ' bit 3 = averaging mode
curr_smp &= %0111 ' capture only the # of samples
return (|<(curr_smp))
else
return 0
samples := ((curr_smp & core#SADC_MASK) | samples)
writereg(core#CONFIG, 2, @samples)
PUB shunt_voltage_data{}: adc_word
' Read shunt voltage ADC word
adc_word := 0
readreg(core#SHUNT_VOLTAGE, 2, @adc_word)
PUB shunt_voltage{}: v
' Read shunt voltage
' Returns: Voltage in microvolts
return adc2shunt_volts(shunt_voltage_data{})
PUB shunt_voltage_rng(range): curr_rng
' Set shunt voltage range, in millivolts
' Valid values: 40, 80, 160, *320
' Any other value polls the chip and returns the current setting
' Example: Setting of 40 means +/- 40mV
curr_rng := 0
readreg(core#CONFIG, 2, @curr_rng)
case range
40, 80, 160, 320:
range := lookdownz(range: 40, 80, 160, 320) << core#PG
other:
curr_rng := (curr_rng >> core#PG) & core#PG_BITS
return lookupz(curr_rng: 40, 80, 160, 320)
range := ((curr_rng & core#PG_MASK) | range)
writereg(core#CONFIG, 2, @range)
PUB voltage_data{}: v
' Read bus voltage
v := 0
readreg(core#BUS_VOLTAGE, 2, @v)
PRI readreg(reg_nr, nr_bytes, ptr_buff) | cmd_pkt
' read nr_bytes from device into ptr_buff
case reg_nr ' validate register
core#CONFIG..core#CALIBRATION:
cmd_pkt.byte[0] := SLAVE_WR | _addr_bits
cmd_pkt.byte[1] := reg_nr
i2c.start{}
i2c.wrblock_lsbf(@cmd_pkt, 2)
i2c.start{}
i2c.write(SLAVE_RD | _addr_bits)
i2c.rdblock_msbf(ptr_buff, 2, i2c#NAK)
i2c.stop{}
other:
return
PRI writereg(reg_nr, nr_bytes, ptr_buff) | cmd_pkt
' write nr_bytes to device from ptr_buff
case reg_nr
core#CONFIG, core#CALIBRATION:
cmd_pkt.byte[0] := SLAVE_WR | _addr_bits
cmd_pkt.byte[1] := reg_nr
cmd_pkt.byte[2] := byte[ptr_buff][1]
cmd_pkt.byte[3] := byte[ptr_buff][0]
i2c.start{}
i2c.wrblock_lsbf(@cmd_pkt, 4)
i2c.stop{}
other:
return
DAT
{
Copyright (c) 2023 Jesse Burt
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and
associated documentation files (the "Software"), to deal in the Software without restriction,
including without limitation the rights to use, copy, modify, merge, publish, distribute,
sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all copies or
substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT
NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,
DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT
OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
}