]> git.karo-electronics.de Git - oswald.git/blobdiff - metawatch/mw_acc.c
Make accelerometer work, interrup driven tilt change mode
[oswald.git] / metawatch / mw_acc.c
index 270f2467304894237492941ebf905ce38960adc7..9c4b0f56572efde16f7adc2bbc26d0cf78f0715f 100644 (file)
@@ -1,12 +1,87 @@
 #include <msp430.h>
 #include <msp430xgeneric.h>
 #include <stdint.h>
+#include <stdio.h>
 
 #include "mw_main.h"
+#include "mw_uart.h"
 
 #include "mw_acc.h"
 
-void mw_init_acc_i2c(void)
+#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: 
+                       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;
@@ -23,49 +98,307 @@ void mw_init_acc_i2c(void)
        ACCELEROMETER_CTL1 &= ~UCSWRST;
 }
 
-/*
- * DMA2 = SPI for LCD
- */
-static void mw_acc_i2c_write_byte(uint8_t byte)
+void mw_acc_disable_i2c(void)
 {
-       ACCELEROMETER_TXBUF = byte;
-       while ((ACCELEROMETER_CTL1 & ACCELEROMETER_IFG) == 0)
-               nop();
+       /* enable reset to hold it */
+       ACCELEROMETER_CTL1 |= UCSWRST;
 }
 
-/* OK this is polling write, but data is small and 400kHz I2C, it should "just work" :) */
-void mw_acc_i2c_write(const uint8_t addr, const void *data, const uint8_t len)
+void mw_acc_i2c_write(uint8_t RegisterAddress, uint8_t *pData, uint8_t Length)
 {
-       int i;
-
-       if (len == 0) {
+       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))
+       while(!(ACCELEROMETER_IFG & UCTXIFG))
                nop();
   
        /* 
-        * clear transmit interrupt flag,
+        * clear transmit interrupt flag, enable interrupt, 
         * send the register address
         */
        ACCELEROMETER_IFG = 0;
+       ACCELEROMETER_IE |= UCTXIE;
+       ACCELEROMETER_TXBUF = RegisterAddress;
 
-       mw_acc_i2c_write_byte(addr);
+       while (AccelerometerBusy)
+               nop();
+
+       while (ACCELEROMETER_CTL1 & UCTXSTP)
+               nop();
 
-       for (i=0; i<len; i++)
-               mw_acc_i2c_write_byte(*(uint8_t *)(data+i));
+       /* 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
+                */
+               ACCELEROMETER_CTL1 |= UCTXSTT;
+  
+               while(ACCELEROMETER_CTL1 & UCTXSTT)
+                       nop();
+
+               ACCELEROMETER_CTL1 |= UCTXSTP;
+       } 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
+
+       ENABLE_ACCELEROMETER_POWER();
+
+       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 = 0x02 /*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
+
+       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);
+}