Skip to content
Permalink
Browse files

pytrack, pysense: Add accelerometer wake-up feature.

  • Loading branch information
Daniel Campora
Daniel Campora committed Sep 24, 2017
1 parent b97faca commit a355ecec8f0bf021aa35a0d6f1b2d9cf85ef38fd
Showing with 253 additions and 37 deletions.
  1. +91 −14 pysense/lib/LIS2HH12.py
  2. +36 −5 pysense/lib/pysense.py
  3. +91 −14 pytrack/lib/LIS2HH12.py
  4. +35 −4 pytrack/lib/pytrack.py
@@ -1,21 +1,41 @@
import math
import time
import struct
from machine import Pin


FULL_SCALE_2G = const(0)
FULL_SCALE_4G = const(2)
FULL_SCALE_8G = const(3)

ODR_POWER_DOWN = const(0)
ODR_10_HZ = const(1)
ODR_50_HZ = const(2)
ODR_100_HZ = const(3)
ODR_200_HZ = const(4)
ODR_400_HZ = const(5)
ODR_800_HZ = const(6)

ACC_G_DIV = 1000 * 65536

class LIS2HH12:

ACC_I2CADDR = const(30)

PRODUCTID_REG = const(0x0F)
CTRL1_REG = const(0x20)
CTRL2_REG = const(0x21)
CTRL3_REG = const(0x22)
CTRL4_REG = const(0x23)
CTRL5_REG = const(0x24)
ACC_X_L_REG = const(0x28)
ACC_X_H_REG = const(0x29)
ACC_Y_L_REG = const(0x2A)
ACC_Y_H_REG = const(0x2B)
ACC_Z_L_REG = const(0x2C)
ACC_Z_H_REG = const(0x2D)

SCALE = const(8192)
ACT_THS = const(0x1E)
ACT_DUR = const(0x1F)

def __init__(self, pysense = None, sda = 'P22', scl = 'P21'):
if pysense is not None:
@@ -25,26 +45,32 @@ def __init__(self, pysense = None, sda = 'P22', scl = 'P21'):
self.i2c = I2C(0, mode=I2C.MASTER, pins=(sda, scl))

self.reg = bytearray(1)

self.odr = 0
self.full_scale = 0
self.x = 0
self.y = 0
self.z = 0
self.int_pin = None
self.act_dur = 0
self.debounced = False

self.scales = {FULL_SCALE_2G: 4000, FULL_SCALE_4G: 8000, FULL_SCALE_8G: 16000}
self.odrs = [0, 10, 50, 100, 200, 400, 800]

whoami = self.i2c.readfrom_mem(ACC_I2CADDR , PRODUCTID_REG, 1)
if (whoami[0] != 0x41):
raise ValueError("Incorrect Product ID")
raise ValueError("LIS2HH12 not found")

# enable acceleration readings
self.i2c.readfrom_mem_into(ACC_I2CADDR , CTRL1_REG, self.reg)
self.reg[0] &= ~0b01110000
self.reg[0] |= 0b00110000
self.i2c.writeto_mem(ACC_I2CADDR , CTRL1_REG, self.reg)
# enable acceleration readings at 50Hz
self.set_odr(ODR_50_HZ)

# change the full-scale to 4g
self.i2c.readfrom_mem_into(ACC_I2CADDR , CTRL4_REG, self.reg)
self.reg[0] &= ~0b00110000
self.reg[0] |= 0b00100000
self.i2c.writeto_mem(ACC_I2CADDR , CTRL4_REG, self.reg)
self.set_full_scale(FULL_SCALE_4G)

# set the interrupt pin as active low and open drain
self.i2c.readfrom_mem_into(ACC_I2CADDR , CTRL5_REG, self.reg)
self.reg[0] |= 0b00000011
self.i2c.writeto_mem(ACC_I2CADDR , CTRL5_REG, self.reg)

# make a first read
self.acceleration()
@@ -56,7 +82,8 @@ def acceleration(self):
self.y = struct.unpack('<h', y)
z = self.i2c.readfrom_mem(ACC_I2CADDR , ACC_Z_L_REG, 2)
self.z = struct.unpack('<h', z)
return (self.x[0] / SCALE, self.y[0] / SCALE, self.z[0] / SCALE)
_mult = self.scales[self.full_scale] / ACC_G_DIV
return (self.x[0] * _mult, self.y[0] * _mult, self.z[0] * _mult)

def roll(self):
div = math.sqrt(math.pow(self.y[0], 2) + math.pow(self.z[0], 2))
@@ -76,3 +103,53 @@ def yaw(self):
if div == 0:
div = 0.01
return (180 / 3.14154) * math.atan(self.y[0] / div)

def set_full_scale(self, scale):
self.i2c.readfrom_mem_into(ACC_I2CADDR , CTRL4_REG, self.reg)
self.reg[0] &= ~0b00110000
self.reg[0] |= (scale & 3) << 4
self.i2c.writeto_mem(ACC_I2CADDR , CTRL4_REG, self.reg)
self.full_scale = scale

def set_odr(self, odr):
self.i2c.readfrom_mem_into(ACC_I2CADDR , CTRL1_REG, self.reg)
self.reg[0] &= ~0b01110000
self.reg[0] |= (odr & 7) << 4
self.i2c.writeto_mem(ACC_I2CADDR , CTRL1_REG, self.reg)
self.odr = odr

def enable_activity_interrupt(self, threshold, duration, handler=None):
# Threshold is in mg, duration is ms
self.act_dur = duration

_ths = int((threshold * self.scales[self.full_scale]) / 2000 / 128) & 0x7F
_dur = int((duration * self.odrs[self.odr]) / 1000 / 8)

self.i2c.writeto_mem(ACC_I2CADDR , ACT_THS, _ths)
self.i2c.writeto_mem(ACC_I2CADDR , ACT_DUR, _dur)

# enable the activity/inactivity interrupt
self.i2c.readfrom_mem_into(ACC_I2CADDR , CTRL3_REG, self.reg)
self.reg[0] |= 0b00100000
self.i2c.writeto_mem(ACC_I2CADDR , CTRL3_REG, self.reg)

self._user_handler = handler
self.int_pin = Pin('P13', mode=Pin.IN)
self.int_pin.callback(trigger=Pin.IRQ_FALLING | Pin.IRQ_RISING, handler=self._int_handler)

def activity(self):
if not self.debounced:
time.sleep_ms(self.act_dur)
self.debounced = True
if self.int_pin():
return True
return False

def _int_handler(self, pin_o):
if self._user_handler is not None:
self._user_handler(pin_o)
else:
if pin_o():
print('Activity interrupt')
else:
print('Inactivity interrupt')
@@ -3,7 +3,7 @@
import time
import pycom

__version__ = '1.1.0'
__version__ = '1.2.0'

EXP_RTC_PERIOD = 7000

@@ -37,6 +37,9 @@ class Pysense:
ADCON0_ADDR = const(0x9D)
ADCON1_ADDR = const(0x9E)

IOCAP_ADDR = const(0x391)
IOCAN_ADDR = const(0x392)

_ADCON0_CHS_POSN = const(0x02)
_ADCON0_ADON_MASK = const(0x01)
_ADCON1_ADCS_POSN = const(0x04)
@@ -63,10 +66,13 @@ def __init__(self, i2c=None, sda='P22', scl='P21'):
self.i2c = i2c
else:
self.i2c = I2C(0, mode=I2C.MASTER, pins=(sda, scl))

self.sda = sda
self.scl = scl
self.clk_cal_factor = 1
self.reg = bytearray(6)
self.wake_int = False

try:
self.read_fw_version()
except Exception:
@@ -150,12 +156,21 @@ def setup_sleep(self, time_s):
self._write(bytes([CMD_SETUP_SLEEP, time_s & 0xFF, (time_s >> 8) & 0xFF, (time_s >> 16) & 0xFF]))

def go_to_sleep(self):
# disable back-up power to the pressure sensor
# disable power to the pressure sensor
self.mask_bits_in_memory(PORTC_ADDR, ~(1 << 7))
self.poke_memory(ADCON0_ADDR, 0) # disable the ADC
self.poke_memory(ANSELA_ADDR, ~(1 << 3)) # Don't touch RA3 so that button wake up works
# disable the ADC
self.poke_memory(ADCON0_ADDR, 0)

if self.wake_int:
# Don't touch RA3 or RA5 so that interrupt wake-up works
self.poke_memory(ANSELA_ADDR, ~((1 << 3) | (1 << 5)))
self.poke_memory(ANSELC_ADDR, ~((1 << 6) | (1 << 7)))
else:
# disable power to the accelerometer, and don't touch RA3 so that button wake-up works
self.poke_memory(ANSELA_ADDR, ~(1 << 3))
self.poke_memory(ANSELC_ADDR, ~(1 << 7))

self.poke_memory(ANSELB_ADDR, 0xFF)
self.poke_memory(ANSELC_ADDR, ~(1 << 7))
self._write(bytes([CMD_GO_SLEEP]), wait=False)
# kill the run pin
Pin('P3', mode=Pin.OUT, value=0)
@@ -185,3 +200,19 @@ def read_battery_voltage(self):
time.sleep_us(100)
adc_val = (self.peek_memory(ADRESH_ADDR) << 2) + (self.peek_memory(ADRESL_ADDR) >> 6)
return (((adc_val * 3.3 * 280) / 1023) / 180) + 0.01 # add 10mV to compensate for the drop in the FET

def setup_int_wake_up(self, rising, falling):
""" rising is for activity detection, falling for inactivity """
wake_int = False
if rising:
self.set_bits_in_memory(IOCAP_ADDR, 1 << 5)
wake_int = True
else:
self.mask_bits_in_memory(IOCAP_ADDR, ~(1 << 5))

if falling:
self.set_bits_in_memory(IOCAN_ADDR, 1 << 5)
wake_int = True
else:
self.mask_bits_in_memory(IOCAN_ADDR, ~(1 << 5))
self.wake_int = wake_int
@@ -1,21 +1,41 @@
import math
import time
import struct
from machine import Pin


FULL_SCALE_2G = const(0)
FULL_SCALE_4G = const(2)
FULL_SCALE_8G = const(3)

ODR_POWER_DOWN = const(0)
ODR_10_HZ = const(1)
ODR_50_HZ = const(2)
ODR_100_HZ = const(3)
ODR_200_HZ = const(4)
ODR_400_HZ = const(5)
ODR_800_HZ = const(6)

ACC_G_DIV = 1000 * 65536

class LIS2HH12:

ACC_I2CADDR = const(30)

PRODUCTID_REG = const(0x0F)
CTRL1_REG = const(0x20)
CTRL2_REG = const(0x21)
CTRL3_REG = const(0x22)
CTRL4_REG = const(0x23)
CTRL5_REG = const(0x24)
ACC_X_L_REG = const(0x28)
ACC_X_H_REG = const(0x29)
ACC_Y_L_REG = const(0x2A)
ACC_Y_H_REG = const(0x2B)
ACC_Z_L_REG = const(0x2C)
ACC_Z_H_REG = const(0x2D)

SCALE = const(8192)
ACT_THS = const(0x1E)
ACT_DUR = const(0x1F)

def __init__(self, pysense = None, sda = 'P22', scl = 'P21'):
if pysense is not None:
@@ -25,26 +45,32 @@ def __init__(self, pysense = None, sda = 'P22', scl = 'P21'):
self.i2c = I2C(0, mode=I2C.MASTER, pins=(sda, scl))

self.reg = bytearray(1)

self.odr = 0
self.full_scale = 0
self.x = 0
self.y = 0
self.z = 0
self.int_pin = None
self.act_dur = 0
self.debounced = False

self.scales = {FULL_SCALE_2G: 4000, FULL_SCALE_4G: 8000, FULL_SCALE_8G: 16000}
self.odrs = [0, 10, 50, 100, 200, 400, 800]

whoami = self.i2c.readfrom_mem(ACC_I2CADDR , PRODUCTID_REG, 1)
if (whoami[0] != 0x41):
raise ValueError("Incorrect Product ID")
raise ValueError("LIS2HH12 not found")

# enable acceleration readings
self.i2c.readfrom_mem_into(ACC_I2CADDR , CTRL1_REG, self.reg)
self.reg[0] &= ~0b01110000
self.reg[0] |= 0b00110000
self.i2c.writeto_mem(ACC_I2CADDR , CTRL1_REG, self.reg)
# enable acceleration readings at 50Hz
self.set_odr(ODR_50_HZ)

# change the full-scale to 4g
self.i2c.readfrom_mem_into(ACC_I2CADDR , CTRL4_REG, self.reg)
self.reg[0] &= ~0b00110000
self.reg[0] |= 0b00100000
self.i2c.writeto_mem(ACC_I2CADDR , CTRL4_REG, self.reg)
self.set_full_scale(FULL_SCALE_4G)

# set the interrupt pin as active low and open drain
self.i2c.readfrom_mem_into(ACC_I2CADDR , CTRL5_REG, self.reg)
self.reg[0] |= 0b00000011
self.i2c.writeto_mem(ACC_I2CADDR , CTRL5_REG, self.reg)

# make a first read
self.acceleration()
@@ -56,7 +82,8 @@ def acceleration(self):
self.y = struct.unpack('<h', y)
z = self.i2c.readfrom_mem(ACC_I2CADDR , ACC_Z_L_REG, 2)
self.z = struct.unpack('<h', z)
return (self.x[0] / SCALE, self.y[0] / SCALE, self.z[0] / SCALE)
_mult = self.scales[self.full_scale] / ACC_G_DIV
return (self.x[0] * _mult, self.y[0] * _mult, self.z[0] * _mult)

def roll(self):
div = math.sqrt(math.pow(self.y[0], 2) + math.pow(self.z[0], 2))
@@ -76,3 +103,53 @@ def yaw(self):
if div == 0:
div = 0.01
return (180 / 3.14154) * math.atan(self.y[0] / div)

def set_full_scale(self, scale):
self.i2c.readfrom_mem_into(ACC_I2CADDR , CTRL4_REG, self.reg)
self.reg[0] &= ~0b00110000
self.reg[0] |= (scale & 3) << 4
self.i2c.writeto_mem(ACC_I2CADDR , CTRL4_REG, self.reg)
self.full_scale = scale

def set_odr(self, odr):
self.i2c.readfrom_mem_into(ACC_I2CADDR , CTRL1_REG, self.reg)
self.reg[0] &= ~0b01110000
self.reg[0] |= (odr & 7) << 4
self.i2c.writeto_mem(ACC_I2CADDR , CTRL1_REG, self.reg)
self.odr = odr

def enable_activity_interrupt(self, threshold, duration, handler=None):
# Threshold is in mg, duration is ms
self.act_dur = duration

_ths = int((threshold * self.scales[self.full_scale]) / 2000 / 128) & 0x7F
_dur = int((duration * self.odrs[self.odr]) / 1000 / 8)

self.i2c.writeto_mem(ACC_I2CADDR , ACT_THS, _ths)
self.i2c.writeto_mem(ACC_I2CADDR , ACT_DUR, _dur)

# enable the activity/inactivity interrupt
self.i2c.readfrom_mem_into(ACC_I2CADDR , CTRL3_REG, self.reg)
self.reg[0] |= 0b00100000
self.i2c.writeto_mem(ACC_I2CADDR , CTRL3_REG, self.reg)

self._user_handler = handler
self.int_pin = Pin('P13', mode=Pin.IN)
self.int_pin.callback(trigger=Pin.IRQ_FALLING | Pin.IRQ_RISING, handler=self._int_handler)

def activity(self):
if not self.debounced:
time.sleep_ms(self.act_dur)
self.debounced = True
if self.int_pin():
return True
return False

def _int_handler(self, pin_o):
if self._user_handler is not None:
self._user_handler(pin_o)
else:
if pin_o():
print('Activity interrupt')
else:
print('Inactivity interrupt')

0 comments on commit a355ece

Please sign in to comment.
You can’t perform that action at this time.