Skip to content

Instantly share code, notes, and snippets.

@duppypro
Last active December 26, 2015 22:49
Show Gist options
  • Star 1 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save duppypro/7225636 to your computer and use it in GitHub Desktop.
Save duppypro/7225636 to your computer and use it in GitHub Desktop.
Sample Electric Imp code for reading i2c registers of MMA8452Q accelerometer See electricimp.com See https://www.sparkfun.com/products/10955 for MMA8452Q accelerometer breakout board
// Sample code using MMA8452Q accelerometer
// Electric Imp Device Squirrel code
// License:
// This code is provided under the Creative Commons Attribution-ShareAlike 3.0 License
// http://creativecommons.org/licenses/by-sa/3.0/us/legalcode
// If you find bugs report to duppypro on github or @duppy #MMA8452Q on twitter
// If you find this useful, send a good word to @duppy #MMA8452Q
// Thanks to @jayrz for finding the first bug.
/////////////////////////////////////////////////
// global constants and variables
const versionString = "MMA8452Q Sample v00.01.2013-10-29a"
//const accelChangeThresh = 500 // change in accel per sample to count as movement. Units of milliGs
pollMMA8452QBusy <- false // guard against interrupt handler collisions FIXME: Is this necessary? Debugging why I get no EA_BIT set error sometimes
///////////////////////////////////////////////
// constants for MMA8452Q i2c registers
// the slave address for this device is set in hardware. Creating a variable to save it here is helpful.
// The SparkFun breakout board defaults to 0x1D, set to 0x1C if SA0 jumper on the bottom of the board is set
const MMA8452Q_ADDR = 0x1D // A '<< 1' is needed. I add the '<< 1' in the helper functions.
//const MM8452Q_ADDR = 0x1C // Use this address if SA0 jumper is set.
const STATUS = 0x00
const ZYXOW_BIT = 0x7 // name_BIT == BIT position of name
const ZYXDR_BIT = 0x3
const OUT_X_MSB = 0x01
const SYSMOD = 0x0B
const SYSMOD_STANDBY = 0x00
const SYSMOD_WAKE = 0x01
const SYSMOD_SLEEP = 0x02
const INT_SOURCE = 0x0C
const SRC_ASLP_BIT = 0x7
const SRC_FF_MT_BIT = 0x2
const SRC_DRDY_BIT = 0x0
const WHO_AM_I = 0x0D
const I_AM_MMA8452Q = 0x2A // read addr WHO_AM_I, expect I_AM_MMA8452Q
const XYZ_DATA_CFG = 0x0E
const FS_2G = 0x00
const FS_4G = 0x01
const FS_8G = 0x02
const HPF_OUT_BIT = 0x5
const HP_FILTER_CUTOFF = 0x0F
const FF_MT_CFG = 0x15
const ELE_BIT = 0x7
const OAE_BIT = 0x6
const XYZEFE_BIT = 0x3 // numBits == 3 (one each for XYZ)
const XYZEFE_ALL = 0x07 // enable all 3 bits
const FF_MT_SRC = 0x16
const EA_BIT = 0x7
const FF_MT_THS = 0x17
const DBCNTM_BIT = 0x7
const THS_BIT = 0x0 // numBits == 7
const FF_MT_COUNT = 0x18
const ASLP_COUNT = 0x29
const CTRL_REG1 = 0x2A
const ASLP_RATE_BIT = 0x6 // numBits == 2
const ASLP_RATE_12p5HZ = 0x1
const ASLP_RATE_1p56HZ = 0x3
const DR_BIT = 0x3 // numBits == 3
const DR_12p5HZ = 0x5
const DR_1p56HZ = 0x7
const LNOISE_BIT = 0x2
const F_READ_BIT = 0x1
const ACTIVE_BIT = 0x0
const CTRL_REG2 = 0x2B
const ST_BIT = 0x7
const RST_BIT = 0x6
const SMODS_BIT = 0x3 // numBits == 2
const SLPE_BIT = 0x2
const MODS_BIT = 0x0 // numBits == 2
const MODS_NORMAL = 0x00
const MODS_LOW_POWER = 0x03
const CTRL_REG3 = 0x2C
const WAKE_FF_MT_BIT = 0x3
const IPOL_BIT = 0x1
const CTRL_REG4 = 0x2D
const INT_EN_ASLP_BIT = 0x7
const INT_EN_LNDPRT_BIT= 0x4
const INT_EN_FF_MT_BIT = 0x2
const INT_EN_DRDY_BIT = 0x0
const CTRL_REG5 = 0x2E
// helper variables for MMA8452Q. These are not const because they may have reason to change dynamically.
i2cRetryPeriod <- 1.0 // seconds to wait before retrying a failed i2c operation
maxG <- FS_4G // what scale to get G readings
i2c <- hardware.i2c89 // now can use i2c.read()
///////////////////////////////////////////////
//define functions
// start with fairly generic i2c helper functions
function readBitField(val, bitPosition, numBits){ // works for 8bit registers
// bitPosition and numBits are not bounds checked
return (val >> bitPosition) & (0x00FF >> (8 - numBits))
}
function readBit(val, bitPosition) { return readBitField(val, bitPosition, 1) }
function writeBitField(val, bitPosition, numBits, newVal) { // works for 8bit registers
// newVal is not bounds checked
return (val & (((0x00FF >> (8 - numBits)) << bitPosition) ^ 0x00FF)) | (newVal << bitPosition)
}
function writeBit(val, bitPosition, newVal) { return writeBitField(val, bitPosition, 1, newVal) }
// Read a single byte from addressToRead and return it as a byte. (The '[0]' causes a byte to return)
function readReg(addressToRead) {
return readSequentialRegs(addressToRead, 1)[0]
}
// Writes a single byte (dataToWrite) into addressToWrite. Returns error code from i2c.write
// Continue retry until success. Caller does not need to check error code
function writeReg(addressToWrite, dataToWrite) {
local err = null
while (err == null) {
err = i2c.write(MMA8452Q_ADDR << 1, format("%c%c", addressToWrite, dataToWrite))
// server.log(format("i2c.write addr=0x%02x data=0x%02x", addressToWrite, dataToWrite))
if (err == null) {
server.error("i2c.write of value " + format("0x%02x", dataToWrite) + " to " + format("0x%02x", addressToWrite) + " failed.")
imp.sleep(i2cRetryPeriod)
server.error("retry i2c.write")
}
}
return err
}
// Read numBytes sequentially, starting at addressToRead
// Continue retry until success. Caller does not need to check error code
function readSequentialRegs(addressToRead, numBytes) {
local data = null
while (data == null) {
data = i2c.read(MMA8452Q_ADDR << 1, format("%c", addressToRead), numBytes)
if (data == null) {
server.error("i2c.read from " + format("0x%02x", addressToRead) + " of " + numBytes + " byte" + ((numBytes > 1) ? "s" : "") + " failed.")
imp.sleep(i2cRetryPeriod)
server.error("retry i2c.read")
}
}
return data
}
// now functions unique to MMA8452Q
function readAccelData() {
local rawData = null // x/y/z accel register data stored here, 3 bytes
local accelData = array(3)
local i
local val
rawData = readSequentialRegs(OUT_X_MSB, 3) // Read the three raw data registers into data array
foreach (i, val in rawData) {
accelData[i] = math.floor(1000.0 * ((val < 128 ? val : val - 256) / ((64 >> maxG) + 0.0)))
// HACK: in above calc maxG just happens to be (log2(full_scale) - 1) see: const for FS_2G, FS_4G, FS_8G
//convert to signed integer milliGs
}
return accelData
}
// Reset the MMA8452Q
function MMA8452QReset() {
local reg
do {
reg = readReg(WHO_AM_I) // Read WHO_AM_I register
if (reg == I_AM_MMA8452Q) {
server.log("Found MMA8452Q. Sending RST command...")
break
} else {
server.error("Could not connect to MMA8452Q: WHO_AM_I reg == " + format("0x%02x", reg))
imp.sleep(i2cRetryPeriod)
}
} while (true)
// send reset command
writeReg(CTRL_REG2, writeBit(readReg(CTRL_REG2), RST_BIT, 1))
do {
reg = readReg(WHO_AM_I) // Read WHO_AM_I register
if (reg == I_AM_MMA8452Q) {
server.log("MMA8452Q is online!")
break
} else {
server.error("Could not connect to MMA8452Q: WHO_AM_I reg == " + format("0x%02x", reg))
imp.sleep(i2cRetryPeriod)
}
} while (true)
}
function MMA8452QSetActive(mode) {
// Sets the MMA8452Q active mode.
// 0 == STANDBY for changing registers
// 1 == ACTIVE for outputting data
writeReg(CTRL_REG1, writeBit(readReg(CTRL_REG1), ACTIVE_BIT, mode))
}
function initMMA8452Q() {
// Initialize the MMA8452Q registers
// See the many application notes for more info on setting all of these registers:
// http://www.freescale.com/webapp/sps/site/prod_summary.jsp?code=MMA8452Q
local reg
MMA8452QReset() // Sometimes imp card resets and MMA8452Q keeps power
// Must be in standby to change registers
// in STANDBY already after RESET//MMA8452QSetActive(0)
// Set up the full scale range to 2, 4, or 8g.
// FIXME: assumes HPF_OUT_BIT in this same register always == 0
writeReg(XYZ_DATA_CFG, maxG)
server.log(format("XYZ_DATA_CFG == 0x%02x", readReg(XYZ_DATA_CFG)))
// setup CTRL_REG1
reg = readReg(CTRL_REG1)
reg = writeBitField(reg, ASLP_RATE_BIT, 2, ASLP_RATE_1p56HZ)
reg = writeBitField(reg, DR_BIT, 3, DR_12p5HZ)
// leave LNOISE_BIT as default off to save power
// Set Fast read mode to read 8bits per xyz instead of 12bits
reg = writeBit(reg, F_READ_BIT, 1)
// set all CTRL_REG1 bit fields in one i2c write
writeReg(CTRL_REG1, reg)
server.log(format("CTRL_REG1 == 0x%02x", readReg(CTRL_REG1)))
// setup CTRL_REG2
reg = readReg(CTRL_REG2)
// set Oversample mode in sleep
reg = writeBitField(reg, SMODS_BIT, 2, MODS_LOW_POWER)
// Enable Auto-SLEEP
//reg = writeBit(reg, SLPE_BIT, 1)
// Disable Auto-SLEEP
reg = writeBit(reg, SLPE_BIT, 0)
// set Oversample mode in wake
reg = writeBitField(reg, MODS_BIT, 2, MODS_LOW_POWER)
// set all CTRL_REG2 bit fields in one i2c write
writeReg(CTRL_REG2, reg)
server.log(format("CTRL_REG2 == 0x%02x", readReg(CTRL_REG2)))
// setup CTRL_REG3
reg = readReg(CTRL_REG3)
// allow Motion to wake from SLEEP
reg = writeBit(reg, WAKE_FF_MT_BIT, 1)
// change Int Polarity
reg = writeBit(reg, IPOL_BIT, 1)
// set all CTRL_REG3 bit fields in one i2c write
writeReg(CTRL_REG3, reg)
server.log(format("CTRL_REG3 == 0x%02x", readReg(CTRL_REG3)))
// setup FF_MT_CFG
reg = readReg(FF_MT_CFG)
// enable ELE_BIT to latch FF_MT_SRC events
reg = writeBit(reg, ELE_BIT, 1)
// enable Motion detection (not Free Fall detection)
reg = writeBit(reg, OAE_BIT, 1)
// enable on all axis x, y, and z
reg = writeBitField(reg, XYZEFE_BIT, 3, XYZEFE_ALL)
// set all FF_MT_CFG bit fields in one i2c write
writeReg(FF_MT_CFG, reg)
server.log(format("FF_MT_CFG == 0x%02x", readReg(FF_MT_CFG)))
// setup Motion threshold to n*0.063. (16 * 0.063 == 1G)
writeReg(FF_MT_THS, 60) // FIXME: this is a shortcut and assumes DBCNTM_BIT is 0
server.log(format("FF_MT_THS == 0x%02x", readReg(FF_MT_THS)))
// setup sleep counter, the time in multiples of 320ms of no activity to enter sleep mode
//dont' use ASLP_COUNT for now, use change in prev AccelData reading
//writeReg(ASLP_COUNT, 10) // 10 * 320ms = 3.2 seconds
//Enable Sleep interrupts
// writeReg(CTRL_REG4, writeBit(readReg(CTRL_REG4), INT_EN_ASLP_BIT, 1))
//Enable Motion interrupts
writeReg(CTRL_REG4, writeBit(readReg(CTRL_REG4), INT_EN_FF_MT_BIT, 1))
// Enable interrupts on every new data
writeReg(CTRL_REG4, writeBit(readReg(CTRL_REG4), INT_EN_DRDY_BIT, 1))
server.log(format("CTRL_REG4 == 0x%02x", readReg(CTRL_REG4)))
MMA8452QSetActive(1) // Set to active to start reading
} // initMMA8452Q
// now application specific functions
function pollMMA8452Q() {
local xyz
local reg
while (pollMMA8452QBusy) {
server.log("pollMMA8452QBusy collision")
// wait herer unitl other instance of int handler is done
// FIXME: I never see this error, probably not neessary, just being paranoid.
}
pollMMA8452QBusy = true // mark as busy
if (hardware.pin1.read() == 1) { // only react to low to high edge
//FIXME: do we need to check status for data ready in all xyz?//log(format("STATUS == 0x%02x", readReg(STATUS)), 80)
reg = readReg(INT_SOURCE)
while (reg != 0x00) {
// server.log(format("INT_SOURCE == 0x%02x", reg))
if (readBit(reg, SRC_DRDY_BIT) == 0x1) {
xyz = readAccelData() // this clears the SRC_DRDY_BIT
server.log(format("%4d %4d %4d", xyz[0], xyz[1], xyz[2]))
// do something with xyz data here
}
if (readBit(reg, SRC_FF_MT_BIT) == 0x1) {
server.log("Interrupt SRC_FF_MT_BIT")
reg = readReg(FF_MT_SRC) // this clears SRC_FF_MT_BIT
imp.setpowersave(false) // go to low latency mode because we detected motion
}
if (readBit(reg, SRC_ASLP_BIT) == 0x1) {
reg = readReg(SYSMOD) // this clears SRC_ASLP_BIT
// server.log(format("Entering SYSMOD 0x%02x", reg))
}
reg = readReg(INT_SOURCE)
} // while (reg != 0x00)
} else {
// server.log("INT2 LOW")
}
pollMMA8452QBusy = false // clear so other inst of int handler can run
} // pollMMA8452Q
////////////////////////////////////////////////////////
// first code starts here
imp.setpowersave(true) // start in low power mode.
// Optimized for case where wakeup was caused by periodic timer, not user activity
// Register with the server
//imp.configure("MMA8452Q 1D6", [], []) // One 6-sided Die
// no in and out []s anymore, using Agent messages
// Send status to know we are alive
server.log("BOOTING " + versionString + " " + hardware.getimpeeid() + "/" + imp.getmacaddress())
server.log("imp software version : " + imp.getsoftwareversion())
// BUGBUG: below needed until newer firmware!? See http://forums.electricimp.com/discussion/comment/4875#Comment_2714
// imp.enableblinkup(true)
// Configure pin1 for wakeup. Connect MMA8452Q INT2 pin to imp pin1.
hardware.pin1.configure(DIGITAL_IN_WAKEUP, pollMMA8452Q)
// set the I2C clock speed. We can do 10 kHz, 50 kHz, 100 kHz, or 400 kHz
// i2c.configure(CLOCK_SPEED_400_KHZ)
i2c.configure(CLOCK_SPEED_100_KHZ) // try to fix i2c read errors. May need 4.7K external pull-up to go to 400_KHZ
initMMA8452Q() // sets up code to run on interrupts from MMA8452Q
pollMMA8452Q() // call first time to get a value on boot.
// No more code to execute so we'll sleep until an interrupt from MMA8452Q.
// Sample functions for using MMA8452Q accelerometer
// Electric Imp Device Squirrel (.nut) code
// end of code
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment