Skip to content

Commit 3f0b2de

Browse files
authored
Merge pull request pycom#39 from pycom/features/PYFW-60_wakeup_INT_pin
* Added feature wakeup on INT pin * Factorise the code from Pysense and Pytrack into Pycoproc
2 parents ffd80e5 + 0f123c7 commit 3f0b2de

File tree

5 files changed

+298
-460
lines changed

5 files changed

+298
-460
lines changed

.gitignore

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,3 +3,5 @@
33
*.e4p
44
*.1
55
*.conf
6+
.DS_Store
7+

examples/accelerometer_wake/main.py

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,12 +9,24 @@
99
py = Pytrack()
1010
# py = Pysense()
1111

12+
# display the reset reason code and the sleep remaining in seconds
13+
# possible values of wakeup reason are:
14+
# WAKE_REASON_ACCELEROMETER = 1
15+
# WAKE_REASON_PUSH_BUTTON = 2
16+
# WAKE_REASON_TIMER = 4
17+
# WAKE_REASON_INT_PIN = 8
18+
print("Wakeup reason: " + str(py.get_wake_reason()) + "; Aproximate sleep remaining: " + str(py.get_sleep_remaining()) + " sec")
19+
time.sleep(0.5)
20+
21+
# enable wakeup source from INT pin
22+
py.setup_int_pin_wake_up(False)
23+
1224
# enable activity and also inactivity interrupts, using the default callback handler
1325
py.setup_int_wake_up(True, True)
1426

1527
acc = LIS2HH12()
1628
# enable the activity/inactivity interrupts
17-
# set the accelereation threshold to 2000mG (2G) and the min duration to 200ms
29+
# set the accelereation threshold to 2000mG (2G) and the min duration to 200ms
1830
acc.enable_activity_interrupt(2000, 200)
1931

2032
# check if we were awaken due to activity

lib/pycoproc/pycoproc.py

Lines changed: 275 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,275 @@
1+
from machine import Pin
2+
from machine import I2C
3+
import time
4+
import pycom
5+
6+
__version__ = '0.0.1'
7+
8+
""" PIC MCU wakeup reason types """
9+
WAKE_REASON_ACCELEROMETER = 1
10+
WAKE_REASON_PUSH_BUTTON = 2
11+
WAKE_REASON_TIMER = 4
12+
WAKE_REASON_INT_PIN = 8
13+
14+
class Pycoproc:
15+
""" class for handling interraction with PIC MCU """
16+
17+
I2C_SLAVE_ADDR = const(8)
18+
19+
CMD_PEEK = const(0x0)
20+
CMD_POKE = const(0x01)
21+
CMD_MAGIC = const(0x02)
22+
CMD_HW_VER = const(0x10)
23+
CMD_FW_VER = const(0x11)
24+
CMD_PROD_ID = const(0x12)
25+
CMD_SETUP_SLEEP = const(0x20)
26+
CMD_GO_SLEEP = const(0x21)
27+
CMD_CALIBRATE = const(0x22)
28+
CMD_BAUD_CHANGE = const(0x30)
29+
CMD_DFU = const(0x31)
30+
31+
REG_CMD = const(0)
32+
REG_ADDRL = const(1)
33+
REG_ADDRH = const(2)
34+
REG_AND = const(3)
35+
REG_OR = const(4)
36+
REG_XOR = const(5)
37+
38+
ANSELA_ADDR = const(0x18C)
39+
ANSELB_ADDR = const(0x18D)
40+
ANSELC_ADDR = const(0x18E)
41+
42+
ADCON0_ADDR = const(0x9D)
43+
ADCON1_ADDR = const(0x9E)
44+
45+
IOCAP_ADDR = const(0x391)
46+
IOCAN_ADDR = const(0x392)
47+
48+
INTCON_ADDR = const(0x0B)
49+
OPTION_REG_ADDR = const(0x95)
50+
51+
_ADCON0_CHS_POSN = const(0x02)
52+
_ADCON0_ADON_MASK = const(0x01)
53+
_ADCON1_ADCS_POSN = const(0x04)
54+
_ADCON0_GO_nDONE_MASK = const(0x02)
55+
56+
ADRESL_ADDR = const(0x09B)
57+
ADRESH_ADDR = const(0x09C)
58+
59+
TRISC_ADDR = const(0x08E)
60+
61+
PORTA_ADDR = const(0x00C)
62+
PORTC_ADDR = const(0x00E)
63+
64+
WPUA_ADDR = const(0x20C)
65+
66+
WAKE_REASON_ADDR = const(0x064C)
67+
MEMORY_BANK_ADDR = const(0x0620)
68+
69+
PCON_ADDR = const(0x096)
70+
STATUS_ADDR = const(0x083)
71+
72+
EXP_RTC_PERIOD = const(7000)
73+
74+
def __init__(self, i2c=None, sda='P22', scl='P21'):
75+
if i2c is not None:
76+
self.i2c = i2c
77+
else:
78+
self.i2c = I2C(0, mode=I2C.MASTER, pins=(sda, scl))
79+
80+
self.sda = sda
81+
self.scl = scl
82+
self.clk_cal_factor = 1
83+
self.reg = bytearray(6)
84+
self.wake_int = False
85+
self.wake_int_pin = False
86+
self.wake_int_pin_rising_edge = True
87+
88+
try:
89+
self.read_fw_version()
90+
except Exception:
91+
time.sleep_ms(2)
92+
try:
93+
# init the ADC for the battery measurements
94+
self.poke_memory(ANSELC_ADDR, 1 << 2)
95+
self.poke_memory(ADCON0_ADDR, (0x06 << _ADCON0_CHS_POSN) | _ADCON0_ADON_MASK)
96+
self.poke_memory(ADCON1_ADDR, (0x06 << _ADCON1_ADCS_POSN))
97+
# enable the pull-up on RA3
98+
self.poke_memory(WPUA_ADDR, (1 << 3))
99+
# make RC5 an input
100+
self.set_bits_in_memory(TRISC_ADDR, 1 << 5)
101+
# set RC6 and RC7 as outputs and enable power to the sensors and the GPS
102+
self.mask_bits_in_memory(TRISC_ADDR, ~(1 << 6))
103+
self.mask_bits_in_memory(TRISC_ADDR, ~(1 << 7))
104+
105+
if self.read_fw_version() < 6:
106+
raise ValueError('Firmware out of date')
107+
108+
except Exception:
109+
raise Exception('Board not detected')
110+
111+
def _write(self, data, wait=True):
112+
self.i2c.writeto(I2C_SLAVE_ADDR, data)
113+
if wait:
114+
self._wait()
115+
116+
def _read(self, size):
117+
return self.i2c.readfrom(I2C_SLAVE_ADDR, size + 1)[1:(size + 1)]
118+
119+
def _wait(self):
120+
count = 0
121+
time.sleep_us(10)
122+
while self.i2c.readfrom(I2C_SLAVE_ADDR, 1)[0] != 0xFF:
123+
time.sleep_us(100)
124+
count += 1
125+
if (count > 500): # timeout after 50ms
126+
raise Exception('Board timeout')
127+
128+
def _send_cmd(self, cmd):
129+
self._write(bytes([cmd]))
130+
131+
def read_hw_version(self):
132+
self._send_cmd(CMD_HW_VER)
133+
d = self._read(2)
134+
return (d[1] << 8) + d[0]
135+
136+
def read_fw_version(self):
137+
self._send_cmd(CMD_FW_VER)
138+
d = self._read(2)
139+
return (d[1] << 8) + d[0]
140+
141+
def read_product_id(self):
142+
self._send_cmd(CMD_PROD_ID)
143+
d = self._read(2)
144+
return (d[1] << 8) + d[0]
145+
146+
def peek_memory(self, addr):
147+
self._write(bytes([CMD_PEEK, addr & 0xFF, (addr >> 8) & 0xFF]))
148+
return self._read(1)[0]
149+
150+
def poke_memory(self, addr, value):
151+
self._write(bytes([CMD_POKE, addr & 0xFF, (addr >> 8) & 0xFF, value & 0xFF]))
152+
153+
def magic_write_read(self, addr, _and=0xFF, _or=0, _xor=0):
154+
self._write(bytes([CMD_MAGIC, addr & 0xFF, (addr >> 8) & 0xFF, _and & 0xFF, _or & 0xFF, _xor & 0xFF]))
155+
return self._read(1)[0]
156+
157+
def toggle_bits_in_memory(self, addr, bits):
158+
self.magic_write_read(addr, _xor=bits)
159+
160+
def mask_bits_in_memory(self, addr, mask):
161+
self.magic_write_read(addr, _and=mask)
162+
163+
def set_bits_in_memory(self, addr, bits):
164+
self.magic_write_read(addr, _or=bits)
165+
166+
def get_wake_reason(self):
167+
""" returns the wakeup reason, a value out of constants WAKE_REASON_* """
168+
return self.peek_memory(WAKE_REASON_ADDR)
169+
170+
def get_sleep_remaining(self):
171+
""" returns the remaining time from sleep, as an interrupt (wakeup source) might have triggered """
172+
c3 = self.peek_memory(WAKE_REASON_ADDR + 3)
173+
c2 = self.peek_memory(WAKE_REASON_ADDR + 2)
174+
c1 = self.peek_memory(WAKE_REASON_ADDR + 1)
175+
time_device_s = (c3 << 16) + (c2 << 8) + c1
176+
# this time is from PIC internal oscilator, so it needs to be adjusted with the calibration value
177+
try:
178+
self.calibrate_rtc()
179+
except Exception:
180+
pass
181+
time_s = int((time_device_s / self.clk_cal_factor) + 0.5) # 0.5 used for round
182+
return time_s
183+
184+
def setup_sleep(self, time_s):
185+
try:
186+
self.calibrate_rtc()
187+
except Exception:
188+
pass
189+
time_s = int((time_s * self.clk_cal_factor) + 0.5) # round to the nearest integer
190+
self._write(bytes([CMD_SETUP_SLEEP, time_s & 0xFF, (time_s >> 8) & 0xFF, (time_s >> 16) & 0xFF]))
191+
192+
def go_to_sleep(self, gps=True):
193+
# enable or disable back-up power to the GPS receiver
194+
if gps:
195+
self.set_bits_in_memory(PORTC_ADDR, 1 << 7)
196+
else:
197+
self.mask_bits_in_memory(PORTC_ADDR, ~(1 << 7))
198+
# disable the ADC
199+
self.poke_memory(ADCON0_ADDR, 0)
200+
201+
if self.wake_int:
202+
# Don't touch RA3, RA5 or RC1 so that interrupt wake-up works
203+
self.poke_memory(ANSELA_ADDR, ~((1 << 3) | (1 << 5)))
204+
self.poke_memory(ANSELC_ADDR, ~((1 << 6) | (1 << 7) | (1 << 1)))
205+
else:
206+
# disable power to the accelerometer, and don't touch RA3 so that button wake-up works
207+
self.poke_memory(ANSELA_ADDR, ~(1 << 3))
208+
self.poke_memory(ANSELC_ADDR, ~(1 << 7))
209+
210+
self.poke_memory(ANSELB_ADDR, 0xFF)
211+
212+
# check if INT pin (PIC RC1), should be used for wakeup
213+
if self.wake_int_pin:
214+
if self.wake_int_pin_rising_edge:
215+
self.set_bits_in_memory(OPTION_REG_ADDR, 1 << 6) # rising edge of INT pin
216+
else:
217+
self.mask_bits_in_memory(OPTION_REG_ADDR, ~(1 << 6)) # falling edge of INT pin
218+
self.mask_bits_in_memory(ANSELC_ADDR, ~(1 << 1)) # disable analog function for RC1 pin
219+
self.set_bits_in_memory(TRISC_ADDR, 1 << 1) # make RC1 input pin
220+
self.mask_bits_in_memory(INTCON_ADDR, ~(1 << 1)) # clear INTF
221+
self.set_bits_in_memory(INTCON_ADDR, 1 << 4) # enable interrupt; set INTE)
222+
223+
self._write(bytes([CMD_GO_SLEEP]), wait=False)
224+
# kill the run pin
225+
Pin('P3', mode=Pin.OUT, value=0)
226+
227+
def calibrate_rtc(self):
228+
# the 1.024 factor is because the PIC LF operates at 31 KHz
229+
# WDT has a frequency divider to generate 1 ms
230+
# and then there is a binary prescaler, e.g., 1, 2, 4 ... 512, 1024 ms
231+
# hence the need for the constant
232+
self._write(bytes([CMD_CALIBRATE]), wait=False)
233+
self.i2c.deinit()
234+
Pin('P21', mode=Pin.IN)
235+
pulses = pycom.pulses_get('P21', 50)
236+
self.i2c.init(mode=I2C.MASTER, pins=(self.sda, self.scl))
237+
try:
238+
period = pulses[2][1] - pulses[0][1]
239+
except:
240+
pass
241+
if period > 0:
242+
self.clk_cal_factor = (EXP_RTC_PERIOD / period) * (1000 / 1024)
243+
244+
def button_pressed(self):
245+
button = self.peek_memory(PORTA_ADDR) & (1 << 3)
246+
return not button
247+
248+
def read_battery_voltage(self):
249+
self.set_bits_in_memory(ADCON0_ADDR, _ADCON0_GO_nDONE_MASK)
250+
time.sleep_us(50)
251+
while self.peek_memory(ADCON0_ADDR) & _ADCON0_GO_nDONE_MASK:
252+
time.sleep_us(100)
253+
adc_val = (self.peek_memory(ADRESH_ADDR) << 2) + (self.peek_memory(ADRESL_ADDR) >> 6)
254+
return (((adc_val * 3.3 * 280) / 1023) / 180) + 0.01 # add 10mV to compensate for the drop in the FET
255+
256+
def setup_int_wake_up(self, rising, falling):
257+
""" rising is for activity detection, falling for inactivity """
258+
wake_int = False
259+
if rising:
260+
self.set_bits_in_memory(IOCAP_ADDR, 1 << 5)
261+
wake_int = True
262+
else:
263+
self.mask_bits_in_memory(IOCAP_ADDR, ~(1 << 5))
264+
265+
if falling:
266+
self.set_bits_in_memory(IOCAN_ADDR, 1 << 5)
267+
wake_int = True
268+
else:
269+
self.mask_bits_in_memory(IOCAN_ADDR, ~(1 << 5))
270+
self.wake_int = wake_int
271+
272+
def setup_int_pin_wake_up(self, rising_edge = True):
273+
""" allows wakeup to be made by the INT pin (PIC -RC1) """
274+
self.wake_int_pin = True
275+
self.wake_int_pin_rising_edge = rising_edge

0 commit comments

Comments
 (0)