blob: f6aa187576e0657a522adf96da644257090cb948 [file] [log] [blame]
Brian Silverman54dd2fe2018-03-16 23:44:31 -07001// This file has the main for the Teensy on the simple receiver board.
2
3#include <inttypes.h>
4#include <stdio.h>
5#include <atomic>
6#include <cmath>
7
Brian Silverman54dd2fe2018-03-16 23:44:31 -07008#include "motors/core/kinetis.h"
Brian Silvermana3a172b2018-03-24 03:53:32 -04009#include "motors/core/time.h"
Brian Silverman54dd2fe2018-03-16 23:44:31 -070010#include "motors/peripheral/adc.h"
11#include "motors/peripheral/can.h"
Brian Silvermana3a172b2018-03-24 03:53:32 -040012#include "motors/peripheral/configuration.h"
Brian Silverman54dd2fe2018-03-16 23:44:31 -070013#include "motors/usb/cdc.h"
Brian Silvermana3a172b2018-03-24 03:53:32 -040014#include "motors/usb/usb.h"
Brian Silverman54dd2fe2018-03-16 23:44:31 -070015#include "motors/util.h"
16
17namespace frc971 {
18namespace motors {
19namespace {
20
21::std::atomic<teensy::AcmTty *> global_stdout{nullptr};
22
Brian Silvermana3a172b2018-03-24 03:53:32 -040023// Last width we received on each channel.
24uint16_t pwm_input_widths[5];
25// When we received a pulse on each channel in milliseconds.
26uint32_t pwm_input_times[5];
27
28// Returns the most recently captured value for the specified input channel
29// scaled from -1 to 1, or 0 if it was captured over 100ms ago.
30float convert_input_width(int channel) {
31 uint16_t width;
32 {
33 DisableInterrupts disable_interrupts;
34 if (time_after(millis(), time_add(pwm_input_times[channel], 100))) {
35 return 0;
36 }
37
38 width = pwm_input_widths[channel];
39 }
40
41 // Values measured with a channel mapped to a button.
42 static constexpr uint16_t kMinWidth = 4133;
43 static constexpr uint16_t kMaxWidth = 7177;
44 if (width < kMinWidth) {
45 width = kMinWidth;
46 } else if (width > kMaxWidth) {
47 width = kMaxWidth;
48 }
49 return (static_cast<float>(2 * (width - kMinWidth)) /
50 static_cast<float>(kMaxWidth - kMinWidth)) -
51 1.0f;
52}
53
54// Sends a SET_RPM command to the specified VESC.
55// Note that sending 6 VESC commands every 1ms doesn't quite fit in the CAN
56// bandwidth.
57void vesc_set_rpm(int vesc_id, float rpm) {
58 const int32_t rpm_int = rpm;
59 uint32_t id = CAN_EFF_FLAG;
60 id |= vesc_id;
61 id |= (0x03 /* SET_RPM */) << 8;
62 uint8_t data[4] = {
63 static_cast<uint8_t>((rpm_int >> 24) & 0xFF),
64 static_cast<uint8_t>((rpm_int >> 16) & 0xFF),
65 static_cast<uint8_t>((rpm_int >> 8) & 0xFF),
66 static_cast<uint8_t>((rpm_int >> 0) & 0xFF),
67 };
68 can_send(id, data, sizeof(data), 2 + vesc_id);
69}
70
71// Sends a SET_CURRENT command to the specified VESC.
72// current is in amps.
73// Note that sending 6 VESC commands every 1ms doesn't quite fit in the CAN
74// bandwidth.
75void vesc_set_current(int vesc_id, float current) {
76 const int32_t current_int = current * 1000;
77 uint32_t id = CAN_EFF_FLAG;
78 id |= vesc_id;
79 id |= (0x01 /* SET_CURRENT */) << 8;
80 uint8_t data[4] = {
81 static_cast<uint8_t>((current_int >> 24) & 0xFF),
82 static_cast<uint8_t>((current_int >> 16) & 0xFF),
83 static_cast<uint8_t>((current_int >> 8) & 0xFF),
84 static_cast<uint8_t>((current_int >> 0) & 0xFF),
85 };
86 can_send(id, data, sizeof(data), 2 + vesc_id);
87}
88
Brian Silverman4f8c6a72018-03-17 23:12:45 -070089void DoVescTest() {
Brian Silverman54dd2fe2018-03-16 23:44:31 -070090 uint32_t time = micros();
91 while (true) {
92 for (int i = 0; i < 6; ++i) {
93 const uint32_t end = time_add(time, 500000);
94 while (true) {
95 const bool done = time_after(micros(), end);
Brian Silverman4f8c6a72018-03-17 23:12:45 -070096 float current;
Brian Silverman54dd2fe2018-03-16 23:44:31 -070097 if (done) {
98 current = -6;
99 } else {
100 current = 6;
101 }
Brian Silvermana3a172b2018-03-24 03:53:32 -0400102 vesc_set_current(i, current);
Brian Silverman54dd2fe2018-03-16 23:44:31 -0700103 if (done) {
104 break;
105 }
106 delay(5);
107 }
108 time = end;
109 }
110 }
111}
112
Brian Silverman4f8c6a72018-03-17 23:12:45 -0700113void DoReceiverTest2() {
Brian Silverman4f8c6a72018-03-17 23:12:45 -0700114 static constexpr float kMaxRpm = 10000.0f;
115 while (true) {
Brian Silvermana3a172b2018-03-24 03:53:32 -0400116 const bool flip = convert_input_width(2) > 0;
Brian Silverman4f8c6a72018-03-17 23:12:45 -0700117
Brian Silverman4f8c6a72018-03-17 23:12:45 -0700118 {
Brian Silvermana3a172b2018-03-24 03:53:32 -0400119 const float value = convert_input_width(0);
Brian Silverman4f8c6a72018-03-17 23:12:45 -0700120
121 {
Brian Silvermana3a172b2018-03-24 03:53:32 -0400122 float rpm = ::std::min(0.0f, value) * kMaxRpm;
Brian Silverman4f8c6a72018-03-17 23:12:45 -0700123 if (flip) {
124 rpm *= -1.0f;
125 }
Brian Silvermana3a172b2018-03-24 03:53:32 -0400126 vesc_set_rpm(0, rpm);
Brian Silverman4f8c6a72018-03-17 23:12:45 -0700127 }
128
129 {
Brian Silvermana3a172b2018-03-24 03:53:32 -0400130 float rpm = ::std::max(0.0f, value) * kMaxRpm;
Brian Silverman4f8c6a72018-03-17 23:12:45 -0700131 if (flip) {
132 rpm *= -1.0f;
133 }
Brian Silvermana3a172b2018-03-24 03:53:32 -0400134 vesc_set_rpm(1, rpm);
Brian Silverman4f8c6a72018-03-17 23:12:45 -0700135 }
136 }
137
Brian Silverman4f8c6a72018-03-17 23:12:45 -0700138 {
Brian Silvermana3a172b2018-03-24 03:53:32 -0400139 const float value = convert_input_width(1);
Brian Silverman4f8c6a72018-03-17 23:12:45 -0700140
141 {
Brian Silvermana3a172b2018-03-24 03:53:32 -0400142 float rpm = ::std::min(0.0f, value) * kMaxRpm;
Brian Silverman4f8c6a72018-03-17 23:12:45 -0700143 if (flip) {
144 rpm *= -1.0f;
145 }
Brian Silvermana3a172b2018-03-24 03:53:32 -0400146 vesc_set_rpm(2, rpm);
Brian Silverman4f8c6a72018-03-17 23:12:45 -0700147 }
148
149 {
Brian Silvermana3a172b2018-03-24 03:53:32 -0400150 float rpm = ::std::max(0.0f, value) * kMaxRpm;
Brian Silverman4f8c6a72018-03-17 23:12:45 -0700151 if (flip) {
152 rpm *= -1.0f;
153 }
Brian Silvermana3a172b2018-03-24 03:53:32 -0400154 vesc_set_rpm(3, rpm);
Brian Silverman4f8c6a72018-03-17 23:12:45 -0700155 }
156 }
157
Brian Silverman4f8c6a72018-03-17 23:12:45 -0700158 {
Brian Silvermana3a172b2018-03-24 03:53:32 -0400159 const float value = convert_input_width(4);
Brian Silverman4f8c6a72018-03-17 23:12:45 -0700160
161 {
Brian Silvermana3a172b2018-03-24 03:53:32 -0400162 float rpm = ::std::min(0.0f, value) * kMaxRpm;
Brian Silverman4f8c6a72018-03-17 23:12:45 -0700163 if (flip) {
164 rpm *= -1.0f;
165 }
Brian Silvermana3a172b2018-03-24 03:53:32 -0400166 vesc_set_rpm(4, rpm);
Brian Silverman4f8c6a72018-03-17 23:12:45 -0700167 }
168
169 {
Brian Silvermana3a172b2018-03-24 03:53:32 -0400170 float rpm = ::std::max(0.0f, value) * kMaxRpm;
Brian Silverman4f8c6a72018-03-17 23:12:45 -0700171 if (flip) {
172 rpm *= -1.0f;
173 }
Brian Silvermana3a172b2018-03-24 03:53:32 -0400174 vesc_set_rpm(5, rpm);
Brian Silverman4f8c6a72018-03-17 23:12:45 -0700175 }
176 }
Brian Silvermana3a172b2018-03-24 03:53:32 -0400177 // Give the CAN frames a chance to go out.
178 delay(5);
Brian Silverman4f8c6a72018-03-17 23:12:45 -0700179 }
180}
181
182void SetupPwmFtm(BigFTM *ftm) {
183 ftm->MODE = FTM_MODE_WPDIS;
184 ftm->MODE = FTM_MODE_WPDIS | FTM_MODE_FTMEN;
185 ftm->SC = FTM_SC_CLKS(0) /* Disable counting for now */;
186
187 // Can't change MOD according to the reference manual ("The Dual Edge Capture
188 // mode must be used with ... the FTM counter in Free running counter.").
189 ftm->MOD = 0xFFFF;
190
191 // Capturing rising edge.
192 ftm->C0SC = FTM_CSC_MSA | FTM_CSC_ELSA;
193 // Capturing falling edge.
Brian Silvermana3a172b2018-03-24 03:53:32 -0400194 ftm->C1SC = FTM_CSC_CHIE | FTM_CSC_MSA | FTM_CSC_ELSB;
Brian Silverman4f8c6a72018-03-17 23:12:45 -0700195
196 // Capturing rising edge.
197 ftm->C2SC = FTM_CSC_MSA | FTM_CSC_ELSA;
198 // Capturing falling edge.
Brian Silvermana3a172b2018-03-24 03:53:32 -0400199 ftm->C3SC = FTM_CSC_CHIE | FTM_CSC_MSA | FTM_CSC_ELSB;
Brian Silverman4f8c6a72018-03-17 23:12:45 -0700200
201 // Capturing rising edge.
202 ftm->C4SC = FTM_CSC_MSA | FTM_CSC_ELSA;
203 // Capturing falling edge.
Brian Silvermana3a172b2018-03-24 03:53:32 -0400204 ftm->C5SC = FTM_CSC_CHIE | FTM_CSC_MSA | FTM_CSC_ELSB;
Brian Silverman4f8c6a72018-03-17 23:12:45 -0700205
206 // Capturing rising edge.
207 ftm->C6SC = FTM_CSC_MSA | FTM_CSC_ELSA;
208 // Capturing falling edge.
Brian Silvermana3a172b2018-03-24 03:53:32 -0400209 ftm->C7SC = FTM_CSC_CHIE | FTM_CSC_MSA | FTM_CSC_ELSB;
Brian Silverman4f8c6a72018-03-17 23:12:45 -0700210
Brian Silvermana3a172b2018-03-24 03:53:32 -0400211 (void)ftm->STATUS;
Brian Silverman4f8c6a72018-03-17 23:12:45 -0700212 ftm->STATUS = 0x00;
213
214 ftm->COMBINE = FTM_COMBINE_DECAP3 | FTM_COMBINE_DECAPEN3 |
215 FTM_COMBINE_DECAP2 | FTM_COMBINE_DECAPEN2 |
216 FTM_COMBINE_DECAP1 | FTM_COMBINE_DECAPEN1 |
217 FTM_COMBINE_DECAP0 | FTM_COMBINE_DECAPEN0;
218
219 // 34.95ms max period before it starts wrapping and being weird.
220 ftm->SC = FTM_SC_CLKS(1) /* Use the system clock */ |
221 FTM_SC_PS(4) /* Prescaler=32 */;
222
223 ftm->MODE &= ~FTM_MODE_WPDIS;
224}
225
Brian Silvermana3a172b2018-03-24 03:53:32 -0400226extern "C" void ftm0_isr() {
227 while (true) {
228 const uint32_t status = FTM0->STATUS;
229 if (status == 0) {
230 return;
231 }
232
233 if (status & (1 << 1)) {
234 const uint32_t start = FTM0->C0V;
235 const uint32_t end = FTM0->C1V;
236 pwm_input_widths[0] = (end - start) & 0xFFFF;
237 pwm_input_times[0] = millis();
238 }
239 if (status & (1 << 7)) {
240 const uint32_t start = FTM0->C6V;
241 const uint32_t end = FTM0->C7V;
242 pwm_input_widths[1] = (end - start) & 0xFFFF;
243 pwm_input_times[1] = millis();
244 }
245 if (status & (1 << 5)) {
246 const uint32_t start = FTM0->C4V;
247 const uint32_t end = FTM0->C5V;
248 pwm_input_widths[2] = (end - start) & 0xFFFF;
249 pwm_input_times[2] = millis();
250 }
251 if (status & (1 << 3)) {
252 const uint32_t start = FTM0->C2V;
253 const uint32_t end = FTM0->C3V;
254 pwm_input_widths[4] = (end - start) & 0xFFFF;
255 pwm_input_times[4] = millis();
256 }
257
258 FTM0->STATUS = 0;
259 }
260}
261
262extern "C" void ftm3_isr() {
263 while (true) {
264 const uint32_t status = FTM3->STATUS;
265 if (status == 0) {
266 return;
267 }
268
269 if (status & (1 << 3)) {
270 const uint32_t start = FTM3->C2V;
271 const uint32_t end = FTM3->C3V;
272 pwm_input_widths[3] = (end - start) & 0xFFFF;
273 pwm_input_times[3] = millis();
274 }
275
276 FTM3->STATUS = 0;
277 }
278}
279
280float ConvertEncoderChannel(uint16_t reading) {
281 // Theoretical values based on the datasheet are 931 and 2917.
282 // With these values, the magnitude ranges from 0.99-1.03, which works fine
283 // (the encoder's output appears to get less accurate in one quadrant for some
284 // reason, hence the variation).
285 static constexpr uint16_t kMin = 802, kMax = 3088;
286 if (reading < kMin) {
287 reading = kMin;
288 } else if (reading > kMax) {
289 reading = kMax;
290 }
291 return (static_cast<float>(2 * (reading - kMin)) /
292 static_cast<float>(kMax - kMin)) -
293 1.0f;
294}
295
296struct EncoderReading {
297 EncoderReading(const salsa::SimpleAdcReadings &adc_readings) {
298 const float sin = ConvertEncoderChannel(adc_readings.sin);
299 const float cos = ConvertEncoderChannel(adc_readings.cos);
300
301 const float magnitude = ::std::sqrt(sin * sin + cos * cos);
302 const float magnitude_error = ::std::abs(magnitude - 1.0f);
303 valid = magnitude_error < 0.15f;
304
305 angle = ::std::atan2(sin, cos);
306 }
307
308 // Angle in radians, in [-pi, pi].
309 float angle;
310
311 bool valid;
312};
313
314extern "C" void pit3_isr() {
315 PIT_TFLG3 = 1;
316
317 salsa::SimpleAdcReadings adc_readings;
318 {
319 DisableInterrupts disable_interrupts;
320 adc_readings = salsa::AdcReadSimple(disable_interrupts);
321 }
322
323 EncoderReading encoder(adc_readings);
324
325 printf("TODO(Austin): 1kHz loop %d %d %d %d %d ADC %" PRIu16 " %" PRIu16
326 " enc %d/1000 %s from %d\n",
327 (int)(convert_input_width(0) * 1000),
328 (int)(convert_input_width(1) * 1000),
329 (int)(convert_input_width(2) * 1000),
330 (int)(convert_input_width(3) * 1000),
331 (int)(convert_input_width(4) * 1000), adc_readings.sin,
332 adc_readings.cos, (int)(encoder.angle * 1000),
333 encoder.valid ? "T" : "f",
334 (int)(::std::sqrt(ConvertEncoderChannel(adc_readings.sin) *
335 ConvertEncoderChannel(adc_readings.sin) +
336 ConvertEncoderChannel(adc_readings.cos) *
337 ConvertEncoderChannel(adc_readings.cos)) *
338 1000));
339}
340
Brian Silverman4f8c6a72018-03-17 23:12:45 -0700341} // namespace
342
343extern "C" {
344
345void *__stack_chk_guard = (void *)0x67111971;
346
347int _write(int /*file*/, char *ptr, int len) {
348 teensy::AcmTty *const tty = global_stdout.load(::std::memory_order_acquire);
349 if (tty != nullptr) {
350 return tty->Write(ptr, len);
351 }
352 return 0;
353}
354
355void __stack_chk_fail(void);
356
Brian Silverman54dd2fe2018-03-16 23:44:31 -0700357} // extern "C"
358
359extern "C" int main(void) {
360 // for background about this startup delay, please see these conversations
361 // https://forum.pjrc.com/threads/36606-startup-time-(400ms)?p=113980&viewfull=1#post113980
362 // https://forum.pjrc.com/threads/31290-Teensey-3-2-Teensey-Loader-1-24-Issues?p=87273&viewfull=1#post87273
363 delay(400);
364
365 // Set all interrupts to the second-lowest priority to start with.
366 for (int i = 0; i < NVIC_NUM_INTERRUPTS; i++) NVIC_SET_SANE_PRIORITY(i, 0xD);
367
368 // Now set priorities for all the ones we care about. They only have meaning
369 // relative to each other, which means centralizing them here makes it a lot
370 // more manageable.
371 NVIC_SET_SANE_PRIORITY(IRQ_USBOTG, 0x7);
Brian Silvermana3a172b2018-03-24 03:53:32 -0400372 NVIC_SET_SANE_PRIORITY(IRQ_FTM0, 0xa);
373 NVIC_SET_SANE_PRIORITY(IRQ_FTM3, 0xa);
374 NVIC_SET_SANE_PRIORITY(IRQ_PIT_CH3, 0x5);
Brian Silverman54dd2fe2018-03-16 23:44:31 -0700375
376 // Builtin LED.
377 PERIPHERAL_BITBAND(GPIOC_PDOR, 5) = 1;
378 PORTC_PCR5 = PORT_PCR_DSE | PORT_PCR_SRE | PORT_PCR_MUX(1);
379 PERIPHERAL_BITBAND(GPIOC_PDDR, 5) = 1;
380
381 // Set up the CAN pins.
382 PORTA_PCR12 = PORT_PCR_DSE | PORT_PCR_MUX(2);
383 PORTA_PCR13 = PORT_PCR_DSE | PORT_PCR_MUX(2);
384
Brian Silverman4f8c6a72018-03-17 23:12:45 -0700385 // PWM_IN0
386 // FTM0_CH0
387 PORTC_PCR1 = PORT_PCR_MUX(4);
388
389 // PWM_IN1
390 // FTM0_CH6
391 PORTD_PCR6 = PORT_PCR_MUX(4);
392
393 // PWM_IN2
394 // FTM0_CH4
395 PORTD_PCR4 = PORT_PCR_MUX(4);
396
397 // PWM_IN3
398 // FTM3_CH2
399 PORTD_PCR2 = PORT_PCR_MUX(4);
400
401 // PWM_IN4
402 // FTM0_CH2
403 PORTC_PCR3 = PORT_PCR_MUX(4);
404
Brian Silverman54dd2fe2018-03-16 23:44:31 -0700405 delay(100);
406
407 teensy::UsbDevice usb_device(0, 0x16c0, 0x0492);
408 usb_device.SetManufacturer("Seems Reasonable LLC");
409 usb_device.SetProduct("Simple Receiver Board");
410
411 teensy::AcmTty tty0(&usb_device);
412 global_stdout.store(&tty0, ::std::memory_order_release);
413 usb_device.Initialize();
414
Brian Silvermana3a172b2018-03-24 03:53:32 -0400415 SIM_SCGC6 |= SIM_SCGC6_PIT;
416 // Workaround for errata e7914.
417 (void)PIT_MCR;
418 PIT_MCR = 0;
419 PIT_LDVAL3 = (BUS_CLOCK_FREQUENCY / 1000) - 1;
420 PIT_TCTRL3 = PIT_TCTRL_TIE | PIT_TCTRL_TEN;
421
Brian Silverman54dd2fe2018-03-16 23:44:31 -0700422 can_init(0, 1);
Brian Silvermana3a172b2018-03-24 03:53:32 -0400423 salsa::AdcInitSimple();
Brian Silverman4f8c6a72018-03-17 23:12:45 -0700424 SetupPwmFtm(FTM0);
425 SetupPwmFtm(FTM3);
Brian Silverman54dd2fe2018-03-16 23:44:31 -0700426
427 // Leave the LEDs on for a bit longer.
428 delay(300);
429 printf("Done starting up\n");
430
431 // Done starting up, now turn the LED off.
432 PERIPHERAL_BITBAND(GPIOC_PDOR, 5) = 0;
433
Brian Silvermana3a172b2018-03-24 03:53:32 -0400434 NVIC_ENABLE_IRQ(IRQ_FTM0);
435 NVIC_ENABLE_IRQ(IRQ_FTM3);
436 NVIC_ENABLE_IRQ(IRQ_PIT_CH3);
437
Brian Silverman4f8c6a72018-03-17 23:12:45 -0700438 DoReceiverTest2();
Brian Silverman54dd2fe2018-03-16 23:44:31 -0700439
440 return 0;
441}
442
443void __stack_chk_fail(void) {
444 while (true) {
445 GPIOC_PSOR = (1 << 5);
446 printf("Stack corruption detected\n");
447 delay(1000);
448 GPIOC_PCOR = (1 << 5);
449 delay(1000);
450 }
451}
452
453} // namespace motors
454} // namespace frc971