Hello. I'm trying to use an accelerometer (the mCube or MEMSIC MC3479) to detect when a user is shaking my device (a dice) in their hand. This accelerometer supports "shake" interrupts, which is the feature I am having trouble getting working.
I successfully got "tilt" interrupts working (though the interrupts only came when I had the dice flat, not when tilted, which seems backward). The only difference in my code between using "tilt" and "shake" interrupts is in the initialization code.
Getting the "tilt" interrupts working (though backward) makes me confident I'm successfully reading and writing with the accelerometer and that my interrupt line coming from INTN1 on the accelerometer going to the microcontroller is working. It makes me believe that the problem is either in the accelerometer itself or in my accelerometer initialization code.
The accelerometer initialization code for "shake" interrupts (not working):
// Set up the accelerometer (ACC) by writing data to it through SPI.
spiWriteToAcc(0x07, 0x00); // enter STANDBY state and disable I2C WDT
while ((spiReadFromAcc(0x05) & 0x03) != 0x00); // wait for the ACC to enter STANDBY state
spiWriteToAcc(0x33, 0x88); // INTN2 = push-pull & active-low. INTN1 = push-pull & active-low.
spiWriteToAcc(0x08, 0x13); // set internal data rate (IDR) to 100Hz
spiWriteToAcc(0x20, 0x20); // 8g range (4096 steps), no low pass filter
spiWriteToAcc(0x2D, 0x08); // FIFO disabled, all interrupts combined onto INTN1
spiWriteToAcc(0x31, 0x00); // don't swap INTN1 and INTN2, 4-wire SPI mode, clear all interrupts together
spiWriteToAcc(0x09, 0x4C); // enable ANYM and SHAKE features, use real-time raw data
spiWriteToAcc(0x46, 0x00); // set SHAKE threshold LSB
spiWriteToAcc(0x47, 0x10); // set SHAKE threshold MSB
spiWriteToAcc(0x48, 0x80); // set SHAKE duration LSB
spiWriteToAcc(0x49, 0x30); // set SHAKE duration MSB and count threshold
spiWriteToAcc(0x06, 0x08); // only enable SHAKE interrupts, don't auto-clear interrupts
spiWriteToAcc(0x07, 0x01); // enter WAKE state and disable I2C WDT
while ((spiReadFromAcc(0x05) & 0x03) != 0x01); // wait for the ACC to enter WAKE state
spiWriteToAcc(0x14, 0x00); // clear any pending interrupts
The accelerometer initialization code for "tilt" interrupts (working but backward):
// Set up the accelerometer (ACC) by writing data to it through SPI.
spiWriteToAcc(0x07, 0x00); // enter STANDBY state and disable I2C WDT
while ((spiReadFromAcc(0x05) & 0x03) != 0x00); // wait for the ACC to enter STANDBY state
spiWriteToAcc(0x33, 0x88); // INTN2 = push-pull & active-low. INTN1 = push-pull & active-low.
spiWriteToAcc(0x08, 0x13); // set internal data rate (IDR) to 100Hz
spiWriteToAcc(0x20, 0x20); // 8g range (4096 steps), no low pass filter
spiWriteToAcc(0x2D, 0x08); // FIFO disabled, all interrupts combined onto INTN1
spiWriteToAcc(0x31, 0x00); // don't swap INTN1 and INTN2, 4-wire SPI mode, clear all interrupts together
spiWriteToAcc(0x09, 0x41); // enable TILT/FLIP feature, use real-time raw data
spiWriteToAcc(0x40, 0x20); // set TILT threshold LSB (lower number = device must be flatter to get interrupts)
spiWriteToAcc(0x41, 0x00); // set TILT threshold MSB
spiWriteToAcc(0x42, 0x04); // set TILT debounce time
spiWriteToAcc(0x06, 0x01); // only enable TILT interrupts, don't auto-clear interrupts
spiWriteToAcc(0x07, 0x01); // enter WAKE state and disable I2C WDT
while ((spiReadFromAcc(0x05) & 0x03) != 0x01); // wait for the ACC to enter WAKE state
spiWriteToAcc(0x14, 0x00); // clear any pending interrupts
Main loop code looking for interrupts from the accelerometer:
int main(void)
{
/* Replace with your application code */
init();
PORTA.OUT = 0x00; // turn all 7 LEDs on
_delay_ms(500);
PORTA.OUT = 0xFE; // turn all 7 LEDs off
// Main loop.
while (1)
{
if ((PORTB.IN & 0x04) == 0x00) // if an interrupt has occurred
{
PORTA.OUT = nextVal(PORTA.OUT);
spiWriteToAcc(0x14, 0x00); // clear the interrupt
_delay_ms(200);
}
}
}
I should note that a "shake" interrupt seems to often trigger when I turn the dice off, back on, and then shake it. I am unable to get a second interrupt to trigger after that without restarting the dice again. This is only true with the "shake" initialization settings in the code above. If I use the following "shake" initialization settings, I very rarely get a single interrupt, and I never get multiple interrupts.
spiWriteToAcc(0x46, 0x00); // set SHAKE threshold LSB
spiWriteToAcc(0x47, 0x10); // set SHAKE threshold MSB
spiWriteToAcc(0x48, 0x02); // set SHAKE duration LSB
spiWriteToAcc(0x49, 0x10); // set SHAKE duration MSB and count threshold
I have tried using "if ((spiReadFromAcc(0x13) & 0x08) != 0x00)" in my main loop instead of using "if ((PORTB.IN & 0x04) == 0x00)", and it seems the "shake" bit in register 0x13 of the accelerometer is reacting to me shaking the dice, but there isn't an interrupt at PORTB if I go back to the larger main loop code I pasted above.
I have attached the relevant section of the schematic for my dice. VCC=3.0V for my testing.
I appreciate any help that can be offered. I have access to a good amount of lab equipment if I should test anything, though my soldering skills are very bad so I'd rather not solder anything unless I have to.
Edit:
In case it helps, here are the functions I use to read and write to/from the accelerometer. The microcontroller is an ATtiny406 in a VQFN package.
uint8_t inline makeAccWriteCommand(uint8_t addr)
{
return (addr & 0x7F); // clear the MSb to tell the ACC this is a write
}
uint8_t inline makeAccReadCommand(uint8_t addr)
{
return (addr | 0x80); // set the MSb to tell the ACC this is a read
}
/**
* Writes 1 byte of data to the accelerometer (ACC) over SPI.
*
* @param addr Register address to write to on ACC.
* @param data The byte to write to the register on the ACC.
*/
void spiWriteToAcc(uint8_t addr, uint8_t data)
{
addr = makeAccWriteCommand(addr); // modify addr to be a write
PORTC.OUTCLR = 0x08; // send SS low/active
SPI0.DATA = addr; // send the register address to the ACC
while (!(SPI0.INTFLAGS & 0x80)); // wait for the data to be fully sent
addr = SPI0.DATA; // clear the interrupt flag by reading INTFLAGS and then DATA
SPI0.DATA = data; // send the register data to the ACC
while (!(SPI0.INTFLAGS & 0x80)); // wait for the data to be fully sent
addr = SPI0.DATA; // clear the interrupt flag by reading INTFLAGS and then DATA
PORTC.OUTSET = 0x08; // send SS high/inactive
}
/**
* Reads 1 byte of data from the accelerometer (ACC) over SPI.
*
* @param addr Register address to read on the ACC.
* @return The byte of data read from the ACC.
*/
uint8_t spiReadFromAcc(uint8_t addr)
{
addr = makeAccReadCommand(addr); // modify addr to be a read
PORTC.OUTCLR = 0x08; // send SS low/active
SPI0.DATA = addr; // send the register address to the ACC
while (!(SPI0.INTFLAGS & 0x80)); // wait for the data to be fully sent
addr = SPI0.DATA; // clear the interrupt flag by reading INTFLAGS and then DATA
SPI0.DATA = 0x00; // send a blank byte while waiting for the ACC to get the data
while (!(SPI0.INTFLAGS & 0x80)); // wait for the data to be fully sent
addr = SPI0.DATA; // clear the interrupt flag by reading INTFLAGS and then DATA
SPI0.DATA = 0x00; // send a blank byte while reading from the ACC
while (!(SPI0.INTFLAGS & 0x80)); // wait for the data to be fully sent
addr = SPI0.DATA; // clear the interrupt flag by reading INTFLAGS and then DATA
PORTC.OUTSET = 0x08; // send SS high/inactive
return addr; // return the data read from the ACC
}