Hello!
I have a working prototype of a project that includes using a BMA400 accelerometer on Sparkfuns custom breakout (SparkFun Micro Triple Axis Accelerometer Breakout - BMA400 (Qwiic)) with a Teensy 4.0 to detect sudden extreme accelerations such as firearm recoil.
My full code follows at the bottom of the post.
I want to avoid false positives and firearm recoil should result in very short but high accelerations, and the BMA400 data sheet says that it can operate in a 16G operating range mode. To save on usage and for simplicity, I am attempting to use the built in interrupt features of the BMA400, and want to set it to only trigger the interrupt when an acceleration in any direction exceeds 3Gs.
However, the problem that I am running into is that despite operating with a measurement range of 16G, it seems the interrupt config is only set up to allow for thresholds of up to ~2Gs?
bma400_gen_int_conf config =
{
.gen_int_thres = 500, // 8mg resolution (eg. gen_int_thres=5 results in 40mg)
.gen_int_dur = 2, // 10ms resolution (eg. gen_int_dur=5 results in 50ms)
.axes_sel = BMA400_AXIS_XYZ_EN, // Which axes to evaluate for interrupts (X/Y/Z in any combination)
.data_src = BMA400_DATA_SRC_ACCEL_FILT_2, // Which filter to use (must be 100Hz, datasheet recommends filter 2)
.criterion_sel = BMA400_ACTIVITY_INT, // Trigger interrupts when active or inactive
.evaluate_axes = BMA400_ANY_AXES_INT, // Logical combining of axes for interrupt condition (OR/AND)
.ref_update = BMA400_UPDATE_EVERY_TIME, // Whether to automatically update reference values
.hysteresis = BMA400_HYST_96_MG, // Hysteresis acceleration for noise rejection
.int_thres_ref_x = 0, // Raw 12-bit acceleration value
.int_thres_ref_y = 0, // Raw 12-bit acceleration value
.int_thres_ref_z = 0, // Raw 12-bit acceleration value (at 4g range (default), 512 = 1g)
.int_chan = BMA400_INT_CHANNEL_1 // Which pin to use for interrupts
};
".gen_int_thres" is of type uint8_t and caps out at 255 so in the code above it overflows. I have checked the documentation of the sparkfun library and the original BOSCH API it was developed off of and can confirm that this is the data type of that config and I wasnt able to find anything else that looked like it would serve this function.
(arduino-BMA400-API/bma400_defs.h at main · telit/arduino-BMA400-API · GitHub)
I'm overall a novice coder and made a lot of this code with AI tools which obviously I take with a grain of salt. Am I missing something here? Is it just simply the case that although the sensor can measure up to 16G its interrupts are only configureable with thresholds of up to 2G? If so, does anyone have suggestions on how I might get around this limitation?
Thank you!
(Other debug info: I am running the accelerometer over i2c and have that working successfully and can successfully collect data from it, and it seems to be accurate - shows ~1G when pointed up and down. Everything else is working as intended, including the accelerometer interrupt, it just triggers on a slight breeze which defeats the point)
Full code:
#include "SevSeg.h"
#include <Encoder.h>
#include <Wire.h>
#include <SparkFun_BMA400_Arduino_Library.h> // SparkFun BMA400
Encoder spoolEncoder(20, 21);
SevSeg sevseg;
BMA400 accelerometer;
uint8_t i2cAddress = BMA400_I2C_ADDRESS_DEFAULT; // 0x14
volatile byte encoderCount = 10;
volatile byte newCount;
int rounds;
int lastRounds = 0;
const int thresholds[] = {23, 44, 65, 86, 108, 129, 150, 171};
const int encoderDatum = 10; //initialize at 10 to allow for a little backlash without overflow (about 1/5")
const int encoderEndOfMag = 179; //Approx position at End of Mag, validate with hardware.
unsigned long loadLedDuration = 100;
unsigned long ledStartTime;
unsigned int debounceDelay = 500;
bool loadLedOn = false;
volatile bool roundInBattery = false;
volatile bool shotDetected = false;
const int loadLedPin = 14;
const int roundLedPin = 15;
const int resetPin = 23;
const int accelInterruptPin = 22; // BMA400 interrupt output to Teensy pin
void setup() {
Serial.begin(9600);
Serial.println("Serial Initialized");
byte numDigits = 1;
byte digitPins[] = {};
byte segmentPins[] = {7, 4, 10, 9, 8, 5, 6, 11};
bool resistorsOnSegments = true;
byte hardwareConfig = COMMON_ANODE;
sevseg.begin(hardwareConfig, numDigits, digitPins, segmentPins, resistorsOnSegments);
sevseg.setBrightness(100);
sevseg.setNumber(0);
spoolEncoder.write(encoderDatum);
//reset click button on pin 23
pinMode(resetPin, INPUT_PULLDOWN);
attachInterrupt(digitalPinToInterrupt(resetPin), resetCount, RISING);
//Loading Blinker LED
pinMode(loadLedPin, OUTPUT); //GREEN: Remember current limiting resistor
//round-in-battery indicator
pinMode(roundLedPin, OUTPUT); //RED: Remember current limiting resistor
// ----- Accelerometer Setup -----
Wire.begin();
while(accelerometer.beginI2C(i2cAddress) != BMA400_OK)
{
// Not connected, inform user
Serial.println("Error: BMA400 not connected, check wiring and I2C address!");
// Wait a bit to see if connection is established
delay(1000);
}
Serial.println("BMA400 connected!");
// Configure accelerometer for low power motion detection
accelerometer.setRange(BMA400_RANGE_16G);
bma400_gen_int_conf config =
{
.gen_int_thres = 500, // 8mg resolution (eg. gen_int_thres=5 results in 40mg)
.gen_int_dur = 2, // 10ms resolution (eg. gen_int_dur=5 results in 50ms)
.axes_sel = BMA400_AXIS_XYZ_EN, // Which axes to evaluate for interrupts (X/Y/Z in any combination)
.data_src = BMA400_DATA_SRC_ACCEL_FILT_2, // Which filter to use (must be 100Hz, datasheet recommends filter 2)
.criterion_sel = BMA400_ACTIVITY_INT, // Trigger interrupts when active or inactive
.evaluate_axes = BMA400_ANY_AXES_INT, // Logical combining of axes for interrupt condition (OR/AND)
.ref_update = BMA400_UPDATE_EVERY_TIME, // Whether to automatically update reference values
.hysteresis = BMA400_HYST_96_MG, // Hysteresis acceleration for noise rejection
.int_thres_ref_x = 0, // Raw 12-bit acceleration value
.int_thres_ref_y = 0, // Raw 12-bit acceleration value
.int_thres_ref_z = 0, // Raw 12-bit acceleration value (at 4g range (default), 512 = 1g)
.int_chan = BMA400_INT_CHANNEL_1 // Which pin to use for interrupts
};
accelerometer.setGeneric1Interrupt(&config);
accelerometer.setInterruptPinMode(BMA400_INT_CHANNEL_1, BMA400_INT_PUSH_PULL_ACTIVE_1);
accelerometer.enableInterrupt(BMA400_GEN1_INT_EN, true);
//accelerometer.setMode(BMA400_MODE_LOW_POWER); Turn on if needed
//accelerometer.setRange(BMA400_RANGE_16G);
pinMode(accelInterruptPin, INPUT_PULLDOWN);
attachInterrupt(digitalPinToInterrupt(accelInterruptPin), accelISR, RISING);
}
void loop() {
accelerometer.getSensorData();
newCount = readStableEncoder();
if (newCount != encoderCount) {
rounds = getRoundsFromCount(newCount);
if (rounds != lastRounds) {
sevseg.setNumber(rounds);
}
encoderCount = newCount;
Serial.println(newCount);
}
if (rounds != lastRounds) {
digitalWrite(loadLedPin, HIGH);
loadLedOn = true;
ledStartTime = millis();
}
if (loadLedOn && millis() - loadLedDuration >= ledStartTime) {
digitalWrite(loadLedPin, LOW);
loadLedOn = false;
}
if (rounds < lastRounds && !roundInBattery) {
roundInBattery = true;
digitalWrite(roundLedPin, HIGH);
}
if (roundInBattery && shotDetected) {
Serial.println("Shot Detected");
roundInBattery = false;
shotDetected = false;
digitalWrite(roundLedPin, LOW);
}
if (millis()%50 == 0) {
Serial.println(accelerometer.data.accelX, 3);
}
lastRounds = rounds;
sevseg.refreshDisplay();
}
int readStableEncoder() {
int first = spoolEncoder.read();
delayMicroseconds(debounceDelay);
int second = spoolEncoder.read();
return (first == second) ? first : encoderCount;
}
int getRoundsFromCount(int count) {
for (int i = 7; i >= 0; i--) {
if (count >= thresholds[i]) return i + 1;
}
return 0;
}
void resetCount() {
static unsigned long last_interrupt_time = 0;
unsigned long interrupt_time = millis();
if (interrupt_time - last_interrupt_time > 500UL) {
if (encoderCount == encoderDatum) {
spoolEncoder.write(encoderEndOfMag);
} else {
spoolEncoder.write(encoderDatum);
}
}
last_interrupt_time = interrupt_time;
}
void accelISR() {
shotDetected = true;
}