blob: d650d4b41efbe0028ebb72720ae8528b93ae015b [file] [log] [blame]
#include "cape/gyro.h"
#include <inttypes.h>
#include <STM32F2XX.h>
#include "cape/util.h"
#include "cape/led.h"
#define printf(...)
#define SPI SPI3
#define SPI_IRQHandler SPI3_IRQHandler
#define SPI_IRQn SPI3_IRQn
#define RCC_APB1ENR_SPIEN RCC_APB1ENR_SPI3EN
#define TIM TIM13
#define TIM_IRQHandler TIM8_UP_TIM13_IRQHandler
#define TIM_IRQn TIM8_UP_TIM13_IRQn
#define RCC_APB1ENR_TIMEN RCC_APB1ENR_TIM13EN
#define CSEL_GPIO GPIOA
#define CSEL_NUM 4
// The header file also contains references to TIM in gyro_read.
struct GyroOutput gyro_output;
// Set when a parity error is detected and cleared before starting a read.
static volatile int parity_error;
// Which byte we're currently waiting to read.
static volatile int receive_byte;
// The first byte that we receive (the most significant one).
static volatile uint16_t high_value;
// 1 if the latest result is potentially bad and 0 if it's good.
static volatile int bad_reading;
// 1 if the gyro is bad and we're not going to get any more readings.
static volatile int bad_gyro;
// The new reading waiting for the next timer cycle to be outputted.
static volatile int16_t new_reading;
struct GyroOutput gyro_output;
// How many times per second to read the gyro value.
#define kGyroReadFrequency 200
// How many times per second to flash the LED.
// Must evenly divide kGyroReadFrequency.
#define kFlashFrequency 10
#define kStartupCycles (kGyroReadFrequency * 2)
#define kZeroingCycles (kGyroReadFrequency * 6)
// An accumulator for all of the values read while zeroing.
int32_t zero_bias = 0;
int startup_cycles_left = kStartupCycles;
int zeroing_cycles_left = kZeroingCycles;
// These are a pair that hold the offset calculated while zeroing.
// full_units_ is the base (in ticks) and remainder_ ranges between 0 and
// kZeroingCycles (like struct timespec). remainder_ is used to calculate which
// cycles to add an additional unit to the result.
int32_t full_units_offset = 0;
int32_t remainder_offset = 0;
// This keeps track of when to add 1 to the read value (using _offset).
int32_t remainder_sum = 0;
int32_t led_flash = 0;
enum State {
STATE_SETUP0,
STATE_SETUP1,
STATE_SETUP2,
STATE_SETUP3,
STATE_READ,
};
static volatile enum State state;
static int setup_counter;
// Switches to new_state in time TIM milliseconds (aka it shows in the TIM ISR).
static void switch_state(enum State new_state, int time) {
TIM->CR1 = 0;
state = new_state;
TIM->CCR1 = time;
TIM->EGR = TIM_EGR_UG;
TIM->CR1 |= TIM_CR1_CEN;
}
static void gyro_setup_failed(void) {
printf("gyro setup failed. stopping\n");
gyro_output.angle = 0;
gyro_output.last_reading_bad = gyro_output.gyro_bad = 1;
gyro_output.initialized = 1;
gyro_output.zeroed = 0;
led_set(LED_ERR, 1);
}
static void gyro_enable_csel(void) {
// Clear the CSEL pin to select it.
// Do it 8 times (9 cycles) to wait for the amount of time the gyro datasheet
// says we need to.
// (1/2/(7.5MHz)+8ns)*120MHz = 8.96
for (int i = 0; i < 8; ++i) gpio_off(CSEL_GPIO, CSEL_NUM);
}
// Blocks until there is space to enqueue data.
static void spi_write(uint16_t data) {
while (!(SPI->SR & SPI_SR_TXE)) {}
SPI->DR = data;
}
static void do_gyro_read(uint32_t data) {
parity_error = 0;
receive_byte = 0;
gyro_enable_csel();
spi_write(data >> 16);
if (__builtin_parity(data & ~1) == 0) data |= 1;
spi_write(data);
}
// Returns all of the non-data bits in the "header" except the parity from
// value.
static uint8_t gyro_status(uint32_t value) {
return (value >> 26) & ~4;
}
// Returns all of the error bits in the "footer" from value.
static uint8_t gyro_errors(uint32_t value) {
return (value >> 1) & 0x7F;
}
static void reading_received(uint32_t reading) {
switch (state) {
case STATE_SETUP0:
if (parity_error) {
switch_state(STATE_SETUP0, 100);
} else {
if (reading != 1) {
printf("gyro unexpected initial response 0x%" PRIx32 "\n", reading);
// There's a chance that we're retrying because of a parity error
// previously, so keep going.
}
// Wait for it to assert the fault conditions before reading them.
switch_state(STATE_SETUP1, 50);
}
break;
case STATE_SETUP1:
if (parity_error) {
switch_state(STATE_SETUP0, 100);
} else {
// Wait for it to clear the fault conditions before reading again.
switch_state(STATE_SETUP2, 50);
}
break;
case STATE_SETUP2:
if (parity_error) {
switch_state(STATE_SETUP0, 100);
} else {
// If it's not reporting self test data.
if (gyro_status(reading) != 2) {
printf("gyro first value 0x%" PRIx32 " not self test data\n",
reading);
switch_state(STATE_SETUP0, 100);
break;
}
// If we don't see all of the errors.
if (gyro_errors(reading) != 0x7F) {
printf("gyro self test value 0x%" PRIx32 " is bad\n", reading);
gyro_setup_failed();
break;
}
// Wait for the sequential transfer delay before reading out the last
// of
// the self test data.
switch_state(STATE_SETUP3, 1);
}
break;
case STATE_SETUP3:
if (parity_error) {
switch_state(STATE_SETUP0, 100);
} else {
// It should still be reporting self test data.
if (gyro_status(reading) != 2) {
printf("gyro second value 0x%" PRIx32 " not self test data\n",
reading);
switch_state(STATE_SETUP0, 100);
break;
}
gyro_output.initialized = 1;
gyro_output.angle = 0;
gyro_output.last_reading_bad = 1; // until we're started up
gyro_output.gyro_bad = bad_gyro = 0;
// Start reading values (after the sequential transfer delay).
switch_state(STATE_READ, 1);
}
break;
case STATE_READ:
if (parity_error) {
bad_reading = 1;
} else {
// This check assumes that the sequence bits are all 0, but they should
// be
// because that's all we send.
if (gyro_status(reading) != 1) {
uint8_t status = gyro_status(reading);
if (status == 0) {
printf("gyro says sensor data is bad\n");
} else {
printf("gyro gave weird status 0x%" PRIx8 "\n", status);
}
bad_reading = 1;
}
if (gyro_errors(reading) != 0) {
uint8_t errors = gyro_errors(reading);
if (errors & ~(1 << 1)) {
bad_reading = 1;
// Error 1 (continuous self-test error) will set status to 0 if it's
// bad
// enough by itself.
}
if (errors & (1 << 6)) {
printf("gyro PLL error\n");
}
if (errors & (1 << 5)) {
printf("gyro quadrature error\n");
}
if (errors & (1 << 4)) {
printf("gyro non-volatile memory error\n");
bad_gyro = 1;
}
if (errors & (1 << 3)) {
printf("gyro volatile memory error\n");
bad_gyro = 1;
}
if (errors & (1 << 2)) {
printf("gyro power error\n");
}
if (errors & (1 << 1)) {
printf("gyro continuous self-test error\n");
}
if (errors & 1) {
printf("gyro unexpected self check mode\n");
}
}
if (bad_gyro) {
bad_reading = 1;
}
new_reading = -(int16_t)(reading >> 10 & 0xFFFF);
}
switch_state(STATE_READ, 1000 / kGyroReadFrequency);
break;
}
}
void SPI_IRQHandler(void) {
uint32_t status = SPI->SR;
if (status & SPI_SR_RXNE) {
uint16_t value = SPI->DR;
if (receive_byte == 0) {
receive_byte = 1;
if (__builtin_parity(value) != 1) {
parity_error = 1;
high_value = 0;
} else {
high_value = value;
}
} else {
uint32_t full_value = high_value << 16 | value;
if (__builtin_parity(full_value) != 1) {
parity_error = 1;
}
// We have to wait for the hardware to finish sending all the bits first.
while (SPI->SR & SPI_SR_BSY) {}
// Do it 8 times (9 cycles) to wait for the amount of time the gyro datasheet
// says we need to.
// (1/2/(7.5MHz)+8ns)*120MHz = 8.96
for (int i = 0; i < 8; ++i) gpio_off(CSEL_GPIO, CSEL_NUM);
// Set the CSEL pin high to deselect it.
gpio_on(CSEL_GPIO, CSEL_NUM);
reading_received(full_value);
}
}
}
void TIM_IRQHandler(void) {
TIM->CR1 &= ~TIM_CR1_CEN;
TIM->SR = ~TIM_SR_CC1IF;
switch (state) {
case STATE_SETUP0:
if (setup_counter++ < 100) {
// Get it started doing a check.
do_gyro_read(0x20000003);
} else {
gyro_setup_failed();
}
break;
case STATE_SETUP1: // Dummy read to clear the old latched state.
case STATE_SETUP2: // Read self-test data.
case STATE_SETUP3: // Read the second latched self-test data.
do_gyro_read(0x20000000);
break;
case STATE_READ:
++led_flash;
if (led_flash < kGyroReadFrequency / kFlashFrequency / 2) {
led_set(LED_HB, 0);
} else {
led_set(LED_HB, 1);
}
if (led_flash >= kGyroReadFrequency / kFlashFrequency) {
led_flash = 0;
}
if (bad_gyro) {
led_set(LED_ERR, 1);
printf("gyro reader giving up because of bad gyro\n");
gyro_output.gyro_bad = 1;
gyro_output.last_reading_bad = 1;
gyro_output.angle = 0;
break;
}
if (startup_cycles_left) {
led_set(LED_Z, 0);
--startup_cycles_left;
if (bad_reading) {
printf("gyro retrying startup wait because of bad reading\n");
startup_cycles_left = kStartupCycles;
}
} else if (zeroing_cycles_left) {
led_set(LED_Z, 1);
--zeroing_cycles_left;
if (bad_reading) {
printf("gyro restarting zeroing because of bad reading\n");
zeroing_cycles_left = kZeroingCycles;
zero_bias = 0;
} else {
zero_bias -= new_reading;
if (zeroing_cycles_left == 0) {
// Do all the nice math
full_units_offset = zero_bias / kZeroingCycles;
remainder_offset = zero_bias % kZeroingCycles;
if (remainder_offset < 0) {
remainder_offset += kZeroingCycles;
--full_units_offset;
}
gyro_output.zeroed = 1;
}
}
} else {
led_set(LED_Z, 0);
int64_t new_angle = gyro_output.angle;
if (!bad_reading) new_angle += new_reading + full_units_offset;
if (remainder_sum >= kZeroingCycles) {
remainder_sum -= kZeroingCycles;
new_angle += 1;
}
gyro_output.angle = new_angle;
gyro_output.last_reading_bad = bad_reading;
remainder_sum += remainder_offset;
}
do_gyro_read(0x20000000);
break;
}
}
void gyro_init(void) {
gyro_output.initialized = 0;
gyro_output.zeroed = 0;
RCC->APB1ENR |= RCC_APB1ENR_SPIEN;
RCC->APB1ENR |= RCC_APB1ENR_TIMEN;
// Set up CSEL.
gpio_setup_out(CSEL_GPIO, CSEL_NUM, 3);
gpio_on(CSEL_GPIO, CSEL_NUM); // deselect it
// Set up SCK, MISO, and MOSI.
gpio_setup_alt(GPIOC, 10, 6); // SCK
gpio_setup_alt(GPIOC, 11, 6); // MISO
gpio_setup_alt(GPIOC, 12, 6); // MOSI
NVIC_SetPriority(SPI_IRQn, 4);
NVIC_EnableIRQ(SPI_IRQn);
NVIC_SetPriority(TIM_IRQn, 5);
NVIC_EnableIRQ(TIM_IRQn);
TIM->CR1 = 0;
TIM->DIER = TIM_DIER_CC1IE;
TIM->CCMR1 = 0;
// Make it generate 1 tick every ms.
TIM->PSC = 60000 - 1;
TIM->EGR = TIM_EGR_UG;
SPI->CR1 = 0; // make sure it's disabled
SPI->CR1 =
SPI_CR1_DFF /* 16 bit frame */ |
SPI_CR1_SSM | SPI_CR1_SSI | /* don't watch for other masters */
1 << 3 /* 30MHz/4 = 7.5MHz */ |
SPI_CR1_MSTR /* master mode */;
SPI->CR2 = SPI_CR2_RXNEIE;
SPI->CR1 |= SPI_CR1_SPE; // enable it
setup_counter = 0;
led_set(LED_Z, 1);
switch_state(STATE_SETUP0, 100);
}