Brian Silverman | 4987694 | 2013-10-11 17:50:26 -0700 | [diff] [blame] | 1 | #include "gyro.h" |
| 2 | |
| 3 | #include <stdio.h> |
| 4 | #include <inttypes.h> |
| 5 | |
| 6 | #include "FreeRTOS.h" |
| 7 | #include "task.h" |
| 8 | #include "partest.h" |
| 9 | |
| 10 | struct GyroOutput gyro_output; |
| 11 | |
| 12 | static void gyro_disable_csel(void) { |
| 13 | // Set the CSEL pin high to deselect it. |
| 14 | GPIO0->FIOSET = 1 << 16; |
| 15 | } |
| 16 | |
| 17 | static void gyro_enable_csel(void) { |
| 18 | // Clear the CSEL pin to select it. |
| 19 | GPIO0->FIOCLR = 1 << 16; |
| 20 | } |
| 21 | |
| 22 | // Blocks until there is data available. |
| 23 | static uint16_t spi_read(void) { |
| 24 | while (!(SSP0->SR & (1 << 2))) {} |
| 25 | return SSP0->DR; |
| 26 | } |
| 27 | |
| 28 | // Blocks until there is space to enqueue data. |
| 29 | static void spi_write(uint16_t data) { |
| 30 | while (!(SSP0->SR & (1 << 1))) {} |
| 31 | SSP0->DR = data; |
| 32 | } |
| 33 | |
| 34 | static uint32_t do_gyro_read(uint32_t data, int *parity_error) { |
| 35 | *parity_error = 0; |
| 36 | |
| 37 | gyro_enable_csel(); |
| 38 | spi_write(data >> 16); |
| 39 | if (__builtin_parity(data & ~1) == 0) data |= 1; |
| 40 | spi_write(data); |
| 41 | |
| 42 | uint16_t high_value = spi_read(); |
| 43 | if (__builtin_parity(high_value) != 1) { |
| 44 | printf("high value 0x%"PRIx16" parity error\n", high_value); |
| 45 | *parity_error = 1; |
| 46 | } |
| 47 | uint16_t low_value = spi_read(); |
| 48 | gyro_disable_csel(); |
Brian Silverman | d36b7d3 | 2013-10-24 15:56:47 -0700 | [diff] [blame] | 49 | uint32_t r = high_value << 16 | low_value; |
| 50 | if (__builtin_parity(r) != 1) { |
| 51 | printf("low value 0x%"PRIx16" parity error (r=%"PRIx32")\n", low_value, r); |
Brian Silverman | 4987694 | 2013-10-11 17:50:26 -0700 | [diff] [blame] | 52 | *parity_error = 1; |
| 53 | } |
| 54 | |
Brian Silverman | d36b7d3 | 2013-10-24 15:56:47 -0700 | [diff] [blame] | 55 | return r; |
Brian Silverman | 4987694 | 2013-10-11 17:50:26 -0700 | [diff] [blame] | 56 | } |
| 57 | |
| 58 | // Returns all of the non-data bits in the "header" except the parity from |
| 59 | // value. |
| 60 | static uint8_t gyro_status(uint32_t value) { |
| 61 | return (value >> 26) & ~4; |
| 62 | } |
| 63 | |
| 64 | // Returns all of the error bits in the "footer" from value. |
| 65 | static uint8_t gyro_errors(uint32_t value) { |
| 66 | return (value >> 1) & 0x7F; |
| 67 | } |
| 68 | |
| 69 | // Performs a read from the gyro. |
| 70 | // Sets *bad_reading to 1 if the result is potentially bad and *bad_gyro to 1 if |
| 71 | // the gyro is bad and we're not going to get any more readings. |
| 72 | static int16_t gyro_read(int *bad_reading, int *bad_gyro) { |
| 73 | *bad_reading = *bad_gyro = 0; |
| 74 | |
| 75 | int parity_error; |
| 76 | uint32_t value = do_gyro_read(0x20000000, &parity_error); |
| 77 | |
| 78 | if (parity_error) { |
| 79 | *bad_reading = 1; |
| 80 | return 0; |
| 81 | } |
| 82 | |
| 83 | // This check assumes that the sequence bits are all 0, but they should be |
| 84 | // because that's all we send. |
| 85 | if (gyro_status(value) != 1) { |
| 86 | uint8_t status = gyro_status(value); |
| 87 | if (status == 0) { |
| 88 | printf("gyro says sensor data is bad\n"); |
| 89 | } else { |
| 90 | printf("gyro gave weird status 0x%"PRIx8"\n", status); |
| 91 | } |
| 92 | *bad_reading = 1; |
| 93 | } |
| 94 | |
| 95 | if (gyro_errors(value) != 0) { |
| 96 | uint8_t errors = gyro_errors(value); |
| 97 | if (errors & ~(1 << 1)) { |
| 98 | *bad_reading = 1; |
| 99 | // Error 1 (continuous self-test error) will set status to 0 if it's bad |
| 100 | // enough by itself. |
| 101 | } |
| 102 | if (errors & (1 << 6)) { |
| 103 | printf("gyro PLL error\n"); |
| 104 | } |
| 105 | if (errors & (1 << 5)) { |
| 106 | printf("gyro quadrature error\n"); |
| 107 | } |
| 108 | if (errors & (1 << 4)) { |
| 109 | printf("gyro non-volatile memory error\n"); |
| 110 | *bad_gyro = 1; |
| 111 | } |
| 112 | if (errors & (1 << 3)) { |
| 113 | printf("gyro volatile memory error\n"); |
| 114 | *bad_gyro = 1; |
| 115 | } |
| 116 | if (errors & (1 << 2)) { |
| 117 | printf("gyro power error\n"); |
| 118 | } |
| 119 | if (errors & (1 << 1)) { |
| 120 | printf("gyro continuous self-test error\n"); |
| 121 | } |
| 122 | if (errors & 1) { |
| 123 | printf("gyro unexpected self check mode\n"); |
| 124 | } |
| 125 | } |
| 126 | if (*bad_gyro) { |
| 127 | *bad_reading = 1; |
| 128 | return 0; |
| 129 | } else { |
| 130 | return -(int16_t)(value >> 10 & 0xFFFF); |
| 131 | } |
| 132 | } |
| 133 | |
| 134 | // Returns 1 if the setup failed or 0 if it succeeded. |
| 135 | static int gyro_setup(void) { |
| 136 | for (int i = 0; i < 100; ++i) { |
| 137 | portTickType wait_time = xTaskGetTickCount(); |
| 138 | int parity_error; |
| 139 | |
| 140 | // Wait for it to start up. |
| 141 | vTaskDelayUntil(&wait_time, 100 / portTICK_RATE_MS); |
| 142 | // Get it started doing a check. |
| 143 | uint32_t value = do_gyro_read(0x20000003, &parity_error); |
| 144 | if (parity_error) continue; |
| 145 | // Its initial response is hardcoded to 1. |
| 146 | if (value != 1) { |
| 147 | printf("gyro unexpected initial response 0x%"PRIx32"\n", value); |
| 148 | // There's a chance that we're retrying because of a parity error |
| 149 | // previously, so keep going. |
| 150 | } |
| 151 | |
| 152 | // Wait for it to assert the fault conditions. |
| 153 | vTaskDelayUntil(&wait_time, 50 / portTICK_RATE_MS); |
| 154 | // Dummy read to clear the old latched state. |
| 155 | do_gyro_read(0x20000000, &parity_error); |
| 156 | if (parity_error) continue; |
| 157 | |
| 158 | // Wait for it to clear the fault conditions. |
| 159 | vTaskDelayUntil(&wait_time, 50 / portTICK_RATE_MS); |
| 160 | value = do_gyro_read(0x20000000, &parity_error); |
| 161 | if (parity_error) continue; |
| 162 | // If it's not reporting self test data. |
| 163 | if (gyro_status(value) != 2) { |
| 164 | printf("gyro first value 0x%"PRIx32" not self test data\n", value); |
| 165 | continue; |
| 166 | } |
| 167 | // If we don't see all of the errors. |
| 168 | if (gyro_errors(value) != 0x7F) { |
| 169 | printf("gyro self test value 0x%"PRIx32" is bad\n", value); |
| 170 | return 1; |
| 171 | } |
| 172 | |
| 173 | // Wait for the sequential transfer delay. |
| 174 | vTaskDelayUntil(&wait_time, 1 / portTICK_RATE_MS); |
| 175 | value = do_gyro_read(0x20000000, &parity_error); |
| 176 | if (parity_error) continue; |
| 177 | // It should still be reporting self test data. |
| 178 | if (gyro_status(value) != 2) { |
| 179 | printf("gyro second value 0x%"PRIx32" not self test data\n", value); |
| 180 | continue; |
| 181 | } |
| 182 | return 0; |
| 183 | } |
| 184 | return 1; |
| 185 | } |
| 186 | |
| 187 | static portTASK_FUNCTION(gyro_read_task, pvParameters) { |
Brian Silverman | d36b7d3 | 2013-10-24 15:56:47 -0700 | [diff] [blame] | 188 | SC->PCONP |= PCONP_PCSSP0; |
Brian Silverman | 74acd62 | 2013-10-26 14:47:14 -0700 | [diff] [blame] | 189 | |
Brian Silverman | d36b7d3 | 2013-10-24 15:56:47 -0700 | [diff] [blame] | 190 | // Set up SSEL. |
| 191 | // It's is just a GPIO pin because we're the master (it would be special if we |
| 192 | // were a slave). |
| 193 | gyro_disable_csel(); |
| 194 | GPIO0->FIODIR |= 1 << 16; |
| 195 | PINCON->PINSEL1 &= ~(3 << 0); |
| 196 | PINCON->PINSEL1 |= 0 << 0; |
| 197 | |
| 198 | // Set up MISO0 and MOSI0. |
| 199 | PINCON->PINSEL1 &= ~(3 << 2 | 3 << 4); |
| 200 | PINCON->PINSEL1 |= 2 << 2 | 2 << 4; |
| 201 | |
| 202 | // Set up SCK0. |
| 203 | PINCON->PINSEL0 &= ~(3 << 30); |
| 204 | PINCON->PINSEL0 |= (2 << 30); |
| 205 | |
| 206 | // Make sure it's disabled. |
| 207 | SSP0->CR1 = 0; |
| 208 | SSP0->CR0 = |
| 209 | 0xF /* 16 bit transfer */ | |
| 210 | 0 << 4 /* SPI mode */ | |
| 211 | 0 << 6 /* CPOL = 0 */ | |
| 212 | 0 << 7 /* CPHA = 0 */; |
| 213 | // 14 clocks per cycle. This works out to a ~7.2MHz bus. |
| 214 | // The gyro is rated for a maximum of 8.08MHz. |
| 215 | SSP0->CPSR = 14; |
| 216 | // Finally, enable it. |
| 217 | // This has to be done after we're done messing with everything else. |
| 218 | SSP0->CR1 |= 1 << 1; |
| 219 | |
| 220 | if (gyro_setup()) { |
| 221 | printf("gyro setup failed. deleting task\n"); |
| 222 | gyro_output.angle = 0; |
| 223 | gyro_output.last_reading_bad = gyro_output.gyro_bad = 1; |
| 224 | gyro_output.initialized = 1; |
| 225 | vTaskDelete(NULL); |
| 226 | return; |
| 227 | } else { |
| 228 | gyro_output.initialized = 1; |
| 229 | } |
| 230 | |
| 231 | gyro_output.angle = 0; |
| 232 | gyro_output.last_reading_bad = 1; // until we're started up |
| 233 | gyro_output.gyro_bad = 0; |
| 234 | |
Brian Silverman | 4987694 | 2013-10-11 17:50:26 -0700 | [diff] [blame] | 235 | // How many times per second to read the gyro value. |
| 236 | static const int kGyroReadFrequency = 200; |
| 237 | // How many times per second to flash the LED. |
| 238 | // Must evenly divide kGyroReadFrequency. |
| 239 | static const int kFlashFrequency = 10; |
| 240 | |
| 241 | static const int kStartupCycles = kGyroReadFrequency * 2; |
| 242 | static const int kZeroingCycles = kGyroReadFrequency * 6; |
| 243 | |
| 244 | // An accumulator for all of the values read while zeroing. |
| 245 | int32_t zero_bias = 0; |
| 246 | |
| 247 | int startup_cycles_left = kStartupCycles; |
| 248 | int zeroing_cycles_left = kZeroingCycles; |
| 249 | |
| 250 | // These are a pair that hold the offset calculated while zeroing. |
| 251 | // full_units_ is the base (in ticks) and remainder_ ranges between 0 and |
| 252 | // kZeroingCycles (like struct timespec). remainder_ is used to calculate which |
| 253 | // cycles to add an additional unit to the result. |
| 254 | int32_t full_units_offset = 0; |
| 255 | int32_t remainder_offset = 0; |
| 256 | // This keeps track of when to add 1 to the read value (using _offset). |
| 257 | int32_t remainder_sum = 0; |
| 258 | |
| 259 | int32_t led_flash = 0; |
| 260 | vParTestSetLED(0, 0); |
| 261 | |
| 262 | portTickType xLastGyroReadTime = xTaskGetTickCount(); |
| 263 | |
| 264 | for (;;) { |
| 265 | ++led_flash; |
| 266 | if (led_flash < kGyroReadFrequency / kFlashFrequency / 2) { |
| 267 | vParTestSetLED(1, 0); |
| 268 | } else { |
| 269 | vParTestSetLED(1, 1); |
| 270 | } |
| 271 | if (led_flash >= kGyroReadFrequency / kFlashFrequency) { |
| 272 | led_flash = 0; |
| 273 | } |
| 274 | |
| 275 | vTaskDelayUntil(&xLastGyroReadTime, |
| 276 | 1000 / kGyroReadFrequency / portTICK_RATE_MS); |
| 277 | |
| 278 | int bad_reading, bad_gyro; |
| 279 | int16_t gyro_value = gyro_read(&bad_reading, &bad_gyro); |
| 280 | if (bad_gyro) { |
| 281 | // We're just going to give up if this happens (write out that we're |
| 282 | // giving up and then never run anything else in this task). |
| 283 | vParTestSetLED(0, 1); |
| 284 | printf("gyro read task giving up because of bad gyro\n"); |
| 285 | portENTER_CRITICAL(); |
| 286 | gyro_output.gyro_bad = 1; |
| 287 | gyro_output.last_reading_bad = 1; |
| 288 | gyro_output.angle = 0; |
| 289 | portEXIT_CRITICAL(); |
| 290 | vTaskDelete(NULL); |
| 291 | while (1) {} |
| 292 | } |
| 293 | |
| 294 | if (startup_cycles_left) { |
| 295 | vParTestSetLED(2, 0); |
| 296 | --startup_cycles_left; |
| 297 | if (bad_reading) { |
| 298 | printf("gyro retrying startup wait because of bad reading\n"); |
| 299 | startup_cycles_left = kStartupCycles; |
| 300 | } |
| 301 | } else if (zeroing_cycles_left) { |
| 302 | vParTestSetLED(2, 1); |
| 303 | --zeroing_cycles_left; |
| 304 | if (bad_reading) { |
| 305 | printf("gyro restarting zeroing because of bad reading\n"); |
| 306 | zeroing_cycles_left = kZeroingCycles; |
| 307 | zero_bias = 0; |
| 308 | } else { |
| 309 | zero_bias -= gyro_value; |
| 310 | if (zeroing_cycles_left == 0) { |
| 311 | // Do all the nice math |
| 312 | full_units_offset = zero_bias / kZeroingCycles; |
| 313 | remainder_offset = zero_bias % kZeroingCycles; |
| 314 | if (remainder_offset < 0) { |
| 315 | remainder_offset += kZeroingCycles; |
| 316 | --full_units_offset; |
| 317 | } |
| 318 | } |
| 319 | } |
| 320 | } else { |
| 321 | vParTestSetLED(2, 0); |
| 322 | |
| 323 | int64_t new_angle = gyro_output.angle; |
| 324 | if (!bad_reading) new_angle += gyro_value + full_units_offset; |
| 325 | if (remainder_sum >= kZeroingCycles) { |
| 326 | remainder_sum -= kZeroingCycles; |
| 327 | new_angle += 1; |
| 328 | } |
| 329 | portENTER_CRITICAL(); |
| 330 | gyro_output.angle = new_angle; |
| 331 | gyro_output.last_reading_bad = bad_reading; |
| 332 | portEXIT_CRITICAL(); |
| 333 | remainder_sum += remainder_offset; |
| 334 | } |
| 335 | } |
| 336 | } |
| 337 | |
| 338 | void gyro_init(void) { |
Brian Silverman | d36b7d3 | 2013-10-24 15:56:47 -0700 | [diff] [blame] | 339 | gyro_output.initialized = 0; |
Brian Silverman | 4987694 | 2013-10-11 17:50:26 -0700 | [diff] [blame] | 340 | |
| 341 | xTaskCreate(gyro_read_task, (signed char *) "gyro", |
| 342 | configMINIMAL_STACK_SIZE + 100, NULL, |
| 343 | tskIDLE_PRIORITY + 2, NULL); |
| 344 | } |