oswald/metawatch/mw_acc.c
2021-02-14 18:03:13 +01:00

423 lines
10 KiB
C

#include <msp430.h>
#include <msp430xgeneric.h>
#include <stdint.h>
#include <stdio.h>
#include "mw_main.h"
#include "mw_uart.h"
#include "mw_acc.h"
#include "oswald_main.h"
#define ACCEL_STATE_DISABLED 0x00
#define ACCEL_STATE_ENABLED 0x01
static uint8_t AccelState;
static uint8_t AccelerometerBusy;
static uint8_t LengthCount;
static uint8_t Index;
static uint8_t *pAccelerometerData;
/*
* Accelerometer is a Kionix KXTF9-4100 connected to I2C
* I2C is pretty slow so reading and writing should be done non blocking
* using interrupts.
*/
#define ACCELEROMETER_NO_INTERRUPTS 0x00
#define ACCELEROMETER_ALIFG 0x02
#define ACCELEROMETER_NACKIFG 0x04
#define ACCELEROMETER_STTIFG 0x06
#define ACCELEROMETER_STPIFG 0x08
#define ACCELEROMETER_RXIFG 0x0a
#define ACCELEROMETER_TXIFG 0x0c
#pragma vector=USCI_B1_VECTOR
__interrupt void ACCERLEROMETER_I2C_ISR(void)
{
// debug_uart_tx("ACC i2c irq\n");
switch (USCI_ACCELEROMETER_IV) {
case ACCELEROMETER_NO_INTERRUPTS:
break;
case ACCELEROMETER_ALIFG:
break;
case ACCELEROMETER_NACKIFG:
nop();
break;
case ACCELEROMETER_STTIFG:
nop();
break;
case ACCELEROMETER_STPIFG:
nop();
break;
case ACCELEROMETER_RXIFG:
if (LengthCount > 0) {
pAccelerometerData[Index++] = ACCELEROMETER_RXBUF;
LengthCount--;
if ( LengthCount == 1 ) {
/* All but one byte received. Send stop */
ACCELEROMETER_CTL1 |= UCTXSTP;
} else if ( LengthCount == 0 ) {
/* Last byte received; disable rx interrupt */
ACCELEROMETER_IE &= ~UCRXIE;
AccelerometerBusy = 0;
}
}
break;
case ACCELEROMETER_TXIFG:
if ( LengthCount > 0 ) {
ACCELEROMETER_TXBUF = pAccelerometerData[Index++];
LengthCount--;
} else {
/* disable transmit interrupt and send stop */
ACCELEROMETER_IE &= ~UCTXIE;
ACCELEROMETER_CTL1 |= UCTXSTP;
AccelerometerBusy = 0;
}
break;
default:
break;
}
}
void mw_acc_init_i2c(void)
{
/* enable reset before configuration */
ACCELEROMETER_CTL1 |= UCSWRST;
/* configure as master using smclk / 40 = 399.5 kHz */
ACCELEROMETER_CTL0 = UCMST + UCMODE_3 + UCSYNC;
ACCELEROMETER_CTL1 = UCSSEL__SMCLK + UCSWRST;
ACCELEROMETER_BR0 = 42;
ACCELEROMETER_BR1 = 0;
ACCELEROMETER_I2CSA = KIONIX_DEVICE_ADDRESS;
/* release reset */
ACCELEROMETER_CTL1 &= ~UCSWRST;
}
void mw_acc_disable_i2c(void)
{
/* enable reset to hold it */
ACCELEROMETER_CTL1 |= UCSWRST;
}
void mw_acc_i2c_write(uint8_t RegisterAddress, uint8_t *pData, uint8_t Length)
{
int tmo;
if (Length == 0 || pData == 0)
return;
while (UCB1STAT & UCBBUSY)
nop();
AccelerometerBusy = 1;
LengthCount = Length;
Index = 0;
pAccelerometerData = pData;
/*
* enable transmit interrupt and
* setup for write and send the start condition
*/
ACCELEROMETER_IFG = 0;
ACCELEROMETER_CTL1 |= UCTR + UCTXSTT;
while(!(ACCELEROMETER_IFG & UCTXIFG))
nop();
/*
* clear transmit interrupt flag, enable interrupt,
* send the register address
*/
ACCELEROMETER_IFG = 0;
ACCELEROMETER_IE |= UCTXIE;
ACCELEROMETER_TXBUF = RegisterAddress;
tmo = 0;
while (AccelerometerBusy) {
while (tmo++ < 1000)
__delay_cycles(16000);
if (tmo >= 1000) {
debug_uart_tx("ACC I2C tx tmo\n");
return;
}
}
while (ACCELEROMETER_CTL1 & UCTXSTP)
nop();
/* the rest of TX will be handled by the ISR */
}
void mw_acc_i2c_read_single(const uint8_t RegisterAddress, const uint8_t *pData)
{
if ( pData == 0 )
return;
/* wait for bus to be free */
while (UCB1STAT & UCBBUSY)
nop();
AccelerometerBusy = 1;
LengthCount = 1;
Index = 0;
pAccelerometerData = (uint8_t *)pData;
/* transmit address */
ACCELEROMETER_IFG = 0;
ACCELEROMETER_CTL1 |= UCTR + UCTXSTT;
while (!(ACCELEROMETER_IFG & UCTXIFG))
nop();
/* write register address */
ACCELEROMETER_IFG = 0;
ACCELEROMETER_TXBUF = RegisterAddress;
while (!(ACCELEROMETER_IFG & UCTXIFG))
nop();
/* send a repeated start (same slave address now it is a read command)
* read possible extra character from rxbuffer
*/
ACCELEROMETER_RXBUF;
ACCELEROMETER_IFG = 0;
ACCELEROMETER_IE |= UCRXIE;
ACCELEROMETER_CTL1 &= ~UCTR;
/* for a read of a single byte the stop must be sent while the byte is being
* received. If this is interrupted an extra byte may be read.
* however, it will be discarded during the next read
*/
if (LengthCount == 1) {
/* errata usci30: prevent interruption of sending stop
* so that only one byte is read
* this requires 62 us @ 320 kHz, 51 @ 400 kHz
*/
__disable_interrupt();
ACCELEROMETER_CTL1 |= UCTXSTT;
while(ACCELEROMETER_CTL1 & UCTXSTT)
nop();
ACCELEROMETER_CTL1 |= UCTXSTP;
__enable_interrupt();
} else {
ACCELEROMETER_CTL1 |= UCTXSTT;
}
/* wait until all data has been received and the stop bit has been sent */
while (AccelerometerBusy)
nop();
while (ACCELEROMETER_CTL1 & UCTXSTP)
nop();
Index = 0;
pAccelerometerData = 0;
}
/* errata usci30: only perform single reads
* second solution: use DMA
*/
void mw_acc_i2c_read(const uint8_t RegisterAddress, uint8_t *pData, const uint8_t Length)
{
int i;
for ( i = 0; i < Length; i++ ) {
mw_acc_i2c_read_single(RegisterAddress + i, (pData + i));
}
}
void mw_acc_init(void)
{
uint8_t WriteRegisterData;
uint8_t pReadRegisterData[4];
#if defined MW_DEVBOARD_V2
char tstr[16];
#endif
// it takes at least 20ms to power up
ENABLE_ACCELEROMETER_POWER();
__delay_cycles(320000);
mw_acc_init_i2c();
/*
* make sure part is in standby mode because some registers can only
* be changed when the part is not active.
*/
WriteRegisterData = PC1_STANDBY_MODE;
mw_acc_i2c_write(KIONIX_CTRL_REG1, &WriteRegisterData, 1);
/* enable face-up and face-down detection */
WriteRegisterData = TILT_FDM | TILT_FUM;
mw_acc_i2c_write(KIONIX_CTRL_REG2, &WriteRegisterData, 1);
/*
* the interrupt from the accelerometer can be used to get periodic data
* the real time clock can also be used
*/
/* change to output data rate to 25 Hz */
WriteRegisterData = WUF_ODR_25HZ | TAP_ODR_400HZ;
mw_acc_i2c_write(KIONIX_CTRL_REG3, &WriteRegisterData, 1);
/* enable interrupt and make it active high */
WriteRegisterData = IEN | IEA;
mw_acc_i2c_write(KIONIX_INT_CTRL_REG1, &WriteRegisterData, 1);
/* enable motion detection interrupt for all three axis */
WriteRegisterData = XBW | YBW | ZBW;
mw_acc_i2c_write(KIONIX_INT_CTRL_REG2, &WriteRegisterData, 1);
/* enable tap interrupt for Z-axis */
WriteRegisterData = TFDM;
mw_acc_i2c_write(KIONIX_INT_CTRL_REG3, &WriteRegisterData, 1);
/* set TDT_TIMER to 0.2 secs*/
WriteRegisterData = 0x50;
mw_acc_i2c_write(KIONIX_TDT_TIMER, &WriteRegisterData, 1);
/* set tap low and high thresholds (default: 26 and 182) */
WriteRegisterData = 40; //78;
mw_acc_i2c_write(KIONIX_TDT_L_THRESH, &WriteRegisterData, 1);
WriteRegisterData = 128;
mw_acc_i2c_write(KIONIX_TDT_H_THRESH, &WriteRegisterData, 1);
/* set WUF_TIMER counter */
WriteRegisterData = 10;
mw_acc_i2c_write(KIONIX_WUF_TIMER, &WriteRegisterData, 1);
/* this causes data to always be sent */
// WriteRegisterData = 0x00;
WriteRegisterData = 0x01 /*0x08*/;
mw_acc_i2c_write(KIONIX_WUF_THRESH, &WriteRegisterData, 1);
/* single byte read test */
mw_acc_i2c_read(KIONIX_DCST_RESP, pReadRegisterData, 1);
#if defined MW_DEVBOARD_V2
snprintf(tstr, 16, "acc DCST 0x%02x\n", pReadRegisterData[0]);
debug_uart_tx(tstr);
#endif
/* multiple byte read test */
mw_acc_i2c_read(KIONIX_WHO_AM_I, pReadRegisterData, 2);
#if defined MW_DEVBOARD_V2
snprintf(tstr, 16, "acc is 0x%02x 0x%02x\n", pReadRegisterData[0], pReadRegisterData[1]);
debug_uart_tx(tstr);
#endif
/*
* KIONIX_CTRL_REG3 and DATA_CTRL_REG can remain at their default values
*
* 50 Hz
*/
#if 0
/* KTXF9 300 uA; KTXI9 165 uA */
WriteRegisterData = PC1_OPERATING_MODE | TAP_ENABLE_TDTE;
/* 180 uA; KTXI9 115 uA */
WriteRegisterData = PC1_OPERATING_MODE | RESOLUTION_8BIT | WUF_ENABLE;
/* 180 uA; KTXI9 8.7 uA */
WriteRegisterData = PC1_OPERATING_MODE | TILT_ENABLE_TPE;
/* 720 uA; KTXI9 330 uA */
WriteRegisterData = PC1_OPERATING_MODE | RESOLUTION_12BIT | WUF_ENABLE;
#endif
/* setup the default for the AccelerometerEnable command */
#if 0
OperatingModeRegister = PC1_OPERATING_MODE | RESOLUTION_12BIT | TAP_ENABLE_TDTE | TILT_ENABLE_TPE; // | WUF_ENABLE;
InterruptControl = INTERRUPT_CONTROL_DISABLE_INTERRUPT;
SidControl = SID_CONTROL_SEND_DATA;
SidAddr = KIONIX_XOUT_L;
SidLength = XYZ_DATA_LENGTH;
AccelState = ACCEL_STATE_INIT;
#endif
}
void mw_acc_enable(void)
{
uint8_t sdata;
mw_acc_init();
sdata = PC1_OPERATING_MODE | RESOLUTION_12BIT | TAP_ENABLE_TDTE | TILT_ENABLE_TPE | WUF_ENABLE;
//sdata = PC1_OPERATING_MODE | RESOLUTION_8BIT | TAP_ENABLE_TDTE | TILT_ENABLE_TPE; // | WUF_ENABLE;
mw_acc_i2c_write(KIONIX_CTRL_REG1, &sdata, 1);
ACCELEROMETER_INT_ENABLE();
mw_acc_i2c_read(KIONIX_INT_REL, &sdata, 1);
AccelState = ACCEL_STATE_ENABLED;
}
void mw_acc_disable(void)
{
uint8_t sdata;
if (AccelState == ACCEL_STATE_ENABLED) {
sdata = PC1_STANDBY_MODE;
mw_acc_i2c_write(KIONIX_CTRL_REG1, &sdata, 1);
ACCELEROMETER_INT_DISABLE();
mw_acc_disable_i2c();
/// DISABLE_ACCELEROMETER_POWER();
AccelState = ACCEL_STATE_DISABLED;
}
}
void mw_acc_read(int16_t *x, int16_t *y, int16_t *z)
{
uint8_t rdata[6];
if (AccelState == ACCEL_STATE_ENABLED) {
mw_acc_i2c_read(KIONIX_XOUT_L, rdata, 6);
*x = rdata[0] | (rdata[1] << 8);
*y = rdata[2] | (rdata[3] << 8);
*z = rdata[4] | (rdata[5] << 8);
} else {
*x = 0;
*y = 0;
*z = 0;
}
}
void mw_acc_handle_irq(void)
{
uint8_t sdata, srcreg1, srcreg2;
#if defined MW_DEVBOARD_V2
char tstr[16];
#endif
if (AccelState != ACCEL_STATE_ENABLED)
return;
mw_acc_i2c_read(KIONIX_INT_SRC_REG1, &srcreg1, 1);
#if defined MW_DEVBOARD_V2
snprintf(tstr, 16, "accsrc1: 0x%02x\n", srcreg1);
debug_uart_tx(tstr);
#endif
mw_acc_i2c_read(KIONIX_INT_SRC_REG2, &srcreg2, 1);
#if defined MW_DEVBOARD_V2
snprintf(tstr, 16, "accsrc2: 0x%02x\n", srcreg2);
debug_uart_tx(tstr);
#endif
if (srcreg1 & INT_TAP_SINGLE) {
};
if (srcreg1 & INT_TAP_DOUBLE) {
};
if (srcreg2 & INT_WUFS) {
int16_t x, y, z;
mw_acc_read(&x, &y, &z);
oswald_handle_accel_event((int8_t)(x / (32768 / 255)), (int8_t)(y / (32768 / 255)), (int8_t)(z / (32768 / 255)));
}
mw_acc_i2c_read(KIONIX_INT_REL, &sdata, 1);
}