-
Notifications
You must be signed in to change notification settings - Fork 1
/
Copy pathatm90e26_SPI.py
223 lines (193 loc) · 8.13 KB
/
atm90e26_SPI.py
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
import machine
import time
import ustruct as struct
SoftReset = 0x00 # Software Reset
SysStatus = 0x01 # System Status
FuncEn = 0x02 # Function Enable
SagTh = 0x03 # Voltage Sag Threshold
SmallPMod = 0x04 # Small-Power Mode
LastData = 0x06 # Last Read/Write SPI/UART Value
LSB = 0x08 # RMS/Power 16-bit LSB
CalStart = 0x20 # Calibration Start Command
PLconstH = 0x21 # High Word of PL_Constant
PLconstL = 0x22 # Low Word of PL_Constant
Lgain = 0x23 # L Line Calibration Gain
Lphi = 0x24 # L Line Calibration Angle
Ngain = 0x25 # N Line Calibration Gain
Nphi = 0x26 # N Line Calibration Angle
PStartTh = 0x27 # Active Startup Power Threshold
PNolTh = 0x28 # Active No-Load Power Threshold
QStartTh = 0x29 # Reactive Startup Power Threshold
QNolTh = 0x2A # Reactive No-Load Power Threshold
MMode = 0x2B # Metering Mode Configuration
CSOne = 0x2C # Checksum 1
AdjStart = 0x30 # Measurement Calibration Start Command
Ugain = 0x31 # Voltage rms Gain
IgainL = 0x32 # L Line Current rms Gain
IgainN = 0x33 # N Line Current rms Gain
Uoffset = 0x34 # Voltage Offset
IoffsetL = 0x35 # L Line Current Offset
IoffsetN = 0x36 # N Line Current Offse
PoffsetL = 0x37 # L Line Active Power Offset
QoffsetL = 0x38 # L Line Reactive Power Offset
PoffsetN = 0x39 # N Line Active Power Offset
QoffsetN = 0x3A # N Line Reactive Power Offset
CSTwo = 0x3B # Checksum 2
APenergy = 0x40 # Forward Active Energy
ANenergy = 0x41 # Reverse Active Energy
ATenergy = 0x42 # Absolute Active Energy
RPenergy = 0x43 # Forward (Inductive) Reactive Energy
Rnenerg = 0x44 # Reverse (Capacitive) Reactive Energy
Rtenergy = 0x45 # Absolute Reactive Energy
EnStatus = 0x46 # Metering Status
Irms = 0x48 # L Line Current rms
Urms = 0x49 # Voltage rms
Pmean = 0x4A # L Line Mean Active Power
Qmean = 0x4B # L Line Mean Reactive Power
Freq = 0x4C # Voltage Frequency
PowerF = 0x4D # L Line Power Factor
Pangle = 0x4E # Phase Angle between Voltage and L Line Current
Smean = 0x4F # L Line Mean Apparent Power
IrmsTwo = 0x68 # N Line Current rms
PmeanTwo = 0x6A # N Line Mean Active Power
QmeanTwo = 0x6B # N Line Mean Reactive Power
PowerFTwo = 0x6D # N Line Power Factor
PangleTwo = 0x6E # Phase Angle between Voltage and N Line Current
SmeanTwo = 0x6F # N Line Mean Apparent Power
class ATM90E26_SPI:
'''
spi - hardware or software SPI implementation
cs - Chip Select pin
'''
def __init__(self, spi, cs):
self.spi = spi
self.cs = cs
self.init_atm90()
'''
rw - True - read, False - write
address - register to operate
val - value to write (if any)
'''
def comm_atm90(self, RW, address, val):
# switch MSB and LSB of value
buf = bytearray(2)
otw_val = struct.pack('>H', val)
# Set read write flag
address |= RW << 7
self.cs.value(False)
time.sleep_us(10)
self.spi.write(bytearray([address]))
''' Must wait 4 us for data to become valid '''
time.sleep_us(4)
# Read data
# Do for each byte in transfer
if(RW):
buf = self.spi.read(2)
else:
self.spi.write(otw_val) # write all the bytes
self.cs.value(1)
return int.from_bytes(buf, 'big')
def init_atm90(self):
self.comm_atm90(False, SoftReset, 0x789A) # Perform soft reset
# Voltage sag irq=1, report on warnout pin=1, energy dir change irq=0
self.comm_atm90(False, FuncEn, 0x0030)
self.comm_atm90(False, SagTh, 0x1F2F) # Voltage sag threshhold
# Set metering calibration values
# Metering calibration startup command. Register 21 to 2B need to be set
self.comm_atm90(False, CalStart, 0x5678)
self.comm_atm90(False, PLconstH, 0x00B9) # PL Constant MSB
self.comm_atm90(False, PLconstL, 0xC1F3) # PL Constant LSB
self.comm_atm90(False, Lgain, 0x1D39) # Line calibration gain
self.comm_atm90(False, Lphi, 0x0000) # Line calibration angle
# Active Startup Power Threshold
self.comm_atm90(False, PStartTh, 0x08BD)
# Active No-Load Power Threshold
self.comm_atm90(False, PNolTh, 0x0000)
# Reactive Startup Power Threshold
self.comm_atm90(False, QStartTh, 0x0AEC)
# Reactive No-Load Power Threshold
self.comm_atm90(False, QNolTh, 0x0000)
# Metering Mode Configuration. All defaults. See pg 31 of datasheet.
self.comm_atm90(False, MMode, 0x9422)
# Write CSOne, as self calculated
self.comm_atm90(False, CSOne, 0x4A34)
print("Checksum 1:")
# Checksum 1. Needs to be calculated based off the above values.
print(hex(self.comm_atm90(True, CSOne, 0x0000)))
# Set measurement calibration values
# Measurement calibration startup command, registers 31-3A
self.comm_atm90(False, AdjStart, 0x5678)
self.comm_atm90(False, Ugain, 0xD464) # Voltage rms gain
self.comm_atm90(False, IgainL, 0x6E49) # L line current gain
self.comm_atm90(False, Uoffset, 0x0000) # Voltage offset
self.comm_atm90(False, IoffsetL, 0x0000) # L line current offset
self.comm_atm90(False, PoffsetL, 0x0000) # L line active power offset
# L line reactive power offset
self.comm_atm90(False, QoffsetL, 0x0000)
# Write CSTwo, as self calculated
self.comm_atm90(False, CSTwo, 0xD294)
print("Checksum 2:")
# Checksum 2. Needs to be calculated based off the above values.
print(hex(self.comm_atm90(True, CSTwo, 0x0000)))
# Checks correctness of 21-2B registers and starts normal metering if ok
self.comm_atm90(False, CalStart, 0x8765)
# Checks correctness of 31-3A registers and starts normal measurement if ok
self.comm_atm90(False, AdjStart, 0x8765)
systemstatus = self.GetSysStatus()
if (systemstatus & 0xC000):
# checksum 1 error
print("Checksum 1 Error!!")
if (systemstatus & 0x3000):
# checksum 2 error
print("Checksum 2 Error!!")
def GetSysStatus(self):
return self.comm_atm90(True, SysStatus, 0x0000)
def GetLineVoltage(self):
voltage = self.comm_atm90(True, Urms, 0xFFFF)
return voltage/100.0
def GetMeterStatus(self):
return self.comm_atm90(True, EnStatus, 0xFFFF)
def GetLineCurrent(self):
current = self.comm_atm90(True, Irms, 0xFFFF)
return current/1000.0
def GetActivePower(self):
# Complement, MSB is signed bit
apower = self.comm_atm90(True, Pmean, 0xFFFF)
return apower/1.0
def GetFrequency(self):
freq = self.comm_atm90(True, Freq, 0xFFFF)
return freq/100.0
def GetPowerFactor(self):
pf = self.comm_atm90(True, PowerF, 0xFFFF) # MSB is signed bit
# if negative
if(pf & 0x8000):
pf = (pf & 0x7FFF)*-1
return pf/1000.0
def GetImportEnergy(self):
# Register is cleared after reading
ienergy = self.comm_atm90(True, APenergy, 0xFFFF)
return ienergy*0.0001 # returns kWh if PL constant set to 1000imp/kWh
def GetExportEnergy(self):
# Register is cleared after reading
eenergy = self.comm_atm90(True, ANenergy, 0xFFFF)
return eenergy*0.0001 # returns kWh if PL constant set to 1000imp/kWh
def test_dual_atm90e26():
sck = machine.Pin(5, machine.Pin.OUT)
mosi = machine.Pin(18, machine.Pin.OUT)
miso = machine.Pin(19, machine.Pin.IN)
cs1 = machine.Pin(15, machine.Pin.OUT)
cs2 = machine.Pin(33, machine.Pin.OUT)
spi = machine.SPI(1, baudrate=200000, bits=8, polarity=1, phase=1,
firstbit=machine.SPI.MSB, sck=sck, mosi=mosi, miso=miso)
all_ics = [ATM90E26_SPI(spi, cs1), ATM90E26_SPI(spi, cs2)]
while True:
for energy_ic in all_ics:
sys_val = energy_ic.GetSysStatus()
print("Sys Status:", hex(sys_val))
met_val = energy_ic.GetMeterStatus()
print("Met Status:", hex(met_val))
voltage = energy_ic.GetLineVoltage()
print("Voltage:", voltage)
current = energy_ic.GetLineCurrent()
print("Current:", current)
time.sleep_ms(1000)