blob: c3fcec96600e32eb247360d86addee7b4543eb75 [file] [log] [blame]
// This file has the main for the Teensy on the simple receiver board v2 in the
// new robot.
#include <inttypes.h>
#include <stdio.h>
#include <atomic>
#include <chrono>
#include <cmath>
#include "frc971/control_loops/drivetrain/polydrivetrain.h"
#include "motors/core/kinetis.h"
#include "motors/core/time.h"
#include "motors/peripheral/configuration.h"
#include "motors/print/print.h"
#include "motors/seems_reasonable/drivetrain_dog_motor_plant.h"
#include "motors/seems_reasonable/polydrivetrain_dog_motor_plant.h"
#include "motors/util.h"
namespace frc971 {
namespace motors {
namespace {
using ::frc971::constants::ShifterHallEffect;
using ::frc971::control_loops::drivetrain::DrivetrainConfig;
using ::frc971::control_loops::drivetrain::OutputT;
using ::frc971::control_loops::drivetrain::PolyDrivetrain;
namespace chrono = ::std::chrono;
const ShifterHallEffect kThreeStateDriveShifter{0.0, 0.0, 0.25, 0.75};
const DrivetrainConfig<float> &GetDrivetrainConfig() {
static DrivetrainConfig<float> kDrivetrainConfig{
::frc971::control_loops::drivetrain::ShifterType::NO_SHIFTER,
::frc971::control_loops::drivetrain::LoopType::OPEN_LOOP,
::frc971::control_loops::drivetrain::GyroType::SPARTAN_GYRO,
::frc971::control_loops::drivetrain::IMUType::IMU_X,
::motors::seems_reasonable::MakeDrivetrainLoop,
::motors::seems_reasonable::MakeVelocityDrivetrainLoop,
::std::function<StateFeedbackLoop<7, 2, 4, float>()>(),
chrono::duration_cast<chrono::nanoseconds>(
chrono::duration<float>(::motors::seems_reasonable::kDt)),
::motors::seems_reasonable::kRobotRadius,
::motors::seems_reasonable::kWheelRadius, ::motors::seems_reasonable::kV,
::motors::seems_reasonable::kHighGearRatio,
::motors::seems_reasonable::kLowGearRatio, ::motors::seems_reasonable::kJ,
::motors::seems_reasonable::kMass, kThreeStateDriveShifter,
kThreeStateDriveShifter, true /* default_high_gear */,
0 /* down_offset */, 0.8 /* wheel_non_linearity */,
1.2 /* quickturn_wheel_multiplier */, 1.5 /* wheel_multiplier */,
};
return kDrivetrainConfig;
};
::std::atomic<PolyDrivetrain<float> *> global_polydrivetrain{nullptr};
// Last width we received on each channel.
uint16_t pwm_input_widths[6];
// When we received a pulse on each channel in milliseconds.
uint32_t pwm_input_times[6];
constexpr int kChannelTimeout = 100;
bool lost_channel(int channel) {
DisableInterrupts disable_interrupts;
if (time_after(millis(),
time_add(pwm_input_times[channel], kChannelTimeout))) {
return true;
}
return false;
}
// Returns the most recently captured value for the specified input channel
// scaled from -1 to 1, or 0 if it was captured over 100ms ago.
float convert_input_width(int channel) {
uint16_t width;
{
DisableInterrupts disable_interrupts;
if (time_after(millis(),
time_add(pwm_input_times[channel], kChannelTimeout))) {
return 0;
}
width = pwm_input_widths[channel];
}
// Values measured with a channel mapped to a button.
static constexpr uint16_t kMinWidth = 4133;
static constexpr uint16_t kMaxWidth = 7177;
if (width < kMinWidth) {
width = kMinWidth;
} else if (width > kMaxWidth) {
width = kMaxWidth;
}
return (static_cast<float>(2 * (width - kMinWidth)) /
static_cast<float>(kMaxWidth - kMinWidth)) -
1.0f;
}
void SetupPwmInFtm(BigFTM *ftm) {
ftm->MODE = FTM_MODE_WPDIS;
ftm->MODE = FTM_MODE_WPDIS | FTM_MODE_FTMEN;
ftm->SC = FTM_SC_CLKS(0) /* Disable counting for now */;
// Can't change MOD according to the reference manual ("The Dual Edge Capture
// mode must be used with ... the FTM counter in Free running counter.").
ftm->MOD = 0xFFFF;
// Capturing rising edge.
ftm->C0SC = FTM_CSC_MSA | FTM_CSC_ELSA;
// Capturing falling edge.
ftm->C1SC = FTM_CSC_CHIE | FTM_CSC_MSA | FTM_CSC_ELSB;
// Capturing rising edge.
ftm->C2SC = FTM_CSC_MSA | FTM_CSC_ELSA;
// Capturing falling edge.
ftm->C3SC = FTM_CSC_CHIE | FTM_CSC_MSA | FTM_CSC_ELSB;
// Capturing rising edge.
ftm->C4SC = FTM_CSC_MSA | FTM_CSC_ELSA;
// Capturing falling edge.
ftm->C5SC = FTM_CSC_CHIE | FTM_CSC_MSA | FTM_CSC_ELSB;
// Capturing rising edge.
ftm->C6SC = FTM_CSC_MSA | FTM_CSC_ELSA;
// Capturing falling edge.
ftm->C7SC = FTM_CSC_CHIE | FTM_CSC_MSA | FTM_CSC_ELSB;
(void)ftm->STATUS;
ftm->STATUS = 0x00;
ftm->COMBINE = FTM_COMBINE_DECAP3 | FTM_COMBINE_DECAPEN3 |
FTM_COMBINE_DECAP2 | FTM_COMBINE_DECAPEN2 |
FTM_COMBINE_DECAP1 | FTM_COMBINE_DECAPEN1 |
FTM_COMBINE_DECAP0 | FTM_COMBINE_DECAPEN0;
// 34.95ms max period before it starts wrapping and being weird.
ftm->SC = FTM_SC_CLKS(1) /* Use the system clock */ |
FTM_SC_PS(4) /* Prescaler=32 */;
ftm->MODE &= ~FTM_MODE_WPDIS;
}
constexpr int kOutputCounts = 37500;
constexpr int kOutputPrescalerShift = 5;
void SetOutputWidth(int output, float ms) {
static constexpr float kScale = static_cast<float>(
static_cast<double>(kOutputCounts) / 20.0 /* milliseconds per period */);
const int width = static_cast<int>(ms * kScale + 0.5f);
switch (output) {
case 0:
FTM3->C0V = width - 1;
break;
case 1:
FTM3->C2V = width - 1;
break;
case 2:
FTM3->C3V = width - 1;
break;
case 3:
FTM3->C1V = width - 1;
break;
}
FTM3->PWMLOAD = FTM_PWMLOAD_LDOK;
}
// 1ms - 2ms (default for a VESC).
void SetupPwmOutFtm(BigFTM *const pwm_ftm) {
// PWMSYNC doesn't matter because we set SYNCMODE down below.
pwm_ftm->MODE = FTM_MODE_WPDIS;
pwm_ftm->MODE = FTM_MODE_WPDIS | FTM_MODE_FTMEN;
pwm_ftm->SC = FTM_SC_CLKS(0) /* Disable counting for now */ |
FTM_SC_PS(kOutputPrescalerShift);
pwm_ftm->CNTIN = 0;
pwm_ftm->CNT = 0;
pwm_ftm->MOD = kOutputCounts - 1;
// High-true edge-aligned mode (turns on at start, off at match).
pwm_ftm->C0SC = FTM_CSC_MSB | FTM_CSC_ELSB;
pwm_ftm->C1SC = FTM_CSC_MSB | FTM_CSC_ELSB;
pwm_ftm->C2SC = FTM_CSC_MSB | FTM_CSC_ELSB;
pwm_ftm->C3SC = FTM_CSC_MSB | FTM_CSC_ELSB;
pwm_ftm->C4SC = FTM_CSC_MSB | FTM_CSC_ELSB;
pwm_ftm->C5SC = FTM_CSC_MSB | FTM_CSC_ELSB;
pwm_ftm->C6SC = FTM_CSC_MSB | FTM_CSC_ELSB;
pwm_ftm->C7SC = FTM_CSC_MSB | FTM_CSC_ELSB;
pwm_ftm->COMBINE = FTM_COMBINE_SYNCEN3 /* Synchronize updates usefully */ |
FTM_COMBINE_SYNCEN2 /* Synchronize updates usefully */ |
FTM_COMBINE_SYNCEN1 /* Synchronize updates usefully */ |
FTM_COMBINE_SYNCEN0 /* Synchronize updates usefully */;
// Initialize all the channels to 0.
pwm_ftm->OUTINIT = 0;
// All of the channels are active high.
pwm_ftm->POL = 0;
pwm_ftm->SYNCONF =
FTM_SYNCONF_HWWRBUF /* Hardware trigger flushes switching points */ |
FTM_SYNCONF_SWWRBUF /* Software trigger flushes switching points */ |
FTM_SYNCONF_SWRSTCNT /* Software trigger resets the count */ |
FTM_SYNCONF_SYNCMODE /* Use the new synchronization mode */;
// Don't want any intermediate loading points.
pwm_ftm->PWMLOAD = 0;
// This has to happen after messing with SYNCONF, and should happen after
// messing with various other things so the values can get flushed out of the
// buffers.
pwm_ftm->SYNC = FTM_SYNC_SWSYNC /* Flush everything out right now */ |
FTM_SYNC_CNTMAX /* Load new values at the end of the cycle */;
// Wait for the software synchronization to finish.
while (pwm_ftm->SYNC & FTM_SYNC_SWSYNC) {
}
pwm_ftm->SC = FTM_SC_TOIE /* Interrupt on overflow */ |
FTM_SC_CLKS(1) /* Use the system clock */ |
FTM_SC_PS(kOutputPrescalerShift);
pwm_ftm->MODE &= ~FTM_MODE_WPDIS;
}
extern "C" void ftm0_isr() {
while (true) {
const uint32_t status = FTM0->STATUS;
if (status == 0) {
return;
}
if (status & (1 << 1)) {
const uint32_t start = FTM0->C0V;
const uint32_t end = FTM0->C1V;
pwm_input_widths[1] = (end - start) & 0xFFFF;
pwm_input_times[1] = millis();
}
if (status & (1 << 5)) {
const uint32_t start = FTM0->C4V;
const uint32_t end = FTM0->C5V;
pwm_input_widths[3] = (end - start) & 0xFFFF;
pwm_input_times[3] = millis();
}
if (status & (1 << 3)) {
const uint32_t start = FTM0->C2V;
const uint32_t end = FTM0->C3V;
pwm_input_widths[4] = (end - start) & 0xFFFF;
pwm_input_times[4] = millis();
}
FTM0->STATUS = 0;
}
}
extern "C" void ftm3_isr() {
while (true) {
const uint32_t status = FTM3->STATUS;
if (status == 0) {
return;
}
FTM3->STATUS = 0;
}
}
extern "C" void pit3_isr() {
PIT_TFLG3 = 1;
PolyDrivetrain<float> *polydrivetrain =
global_polydrivetrain.load(::std::memory_order_acquire);
const bool lost_drive_channel = lost_channel(3) || lost_channel(1);
if (false) {
static int count = 0;
if (++count == 50) {
count = 0;
printf("0: %d 1: %d\n", (int)pwm_input_widths[3],
(int)pwm_input_widths[1]);
}
}
if (polydrivetrain != nullptr) {
float throttle;
float wheel;
if (lost_drive_channel) {
throttle = 0.0f;
wheel = 0.0f;
} else {
throttle = convert_input_width(1);
wheel = -convert_input_width(3);
}
const bool quickturn = ::std::abs(polydrivetrain->velocity()) < 0.25f;
if (false) {
static int count = 0;
if (++count == 50) {
count = 0;
printf("throttle: %d wheel: %d\n", (int)(throttle * 100),
(int)(wheel * 100));
}
}
OutputT output;
polydrivetrain->SetGoal(wheel, throttle, quickturn, false);
polydrivetrain->Update(12.0f);
polydrivetrain->SetOutput(&output);
if (false) {
static int count = 0;
if (++count == 50) {
count = 0;
printf("l: %d r: %d\n", (int)(output.left_voltage * 100),
(int)(output.right_voltage * 100));
}
}
SetOutputWidth(0, 1.5f - output.left_voltage / 12.0f * 0.5f);
SetOutputWidth(1, 1.5f + output.right_voltage / 12.0f * 0.5f);
}
}
} // namespace
extern "C" {
void *__stack_chk_guard = (void *)0x67111971;
void __stack_chk_fail(void);
} // extern "C"
extern "C" int main(void) {
// for background about this startup delay, please see these conversations
// https://forum.pjrc.com/threads/36606-startup-time-(400ms)?p=113980&viewfull=1#post113980
// https://forum.pjrc.com/threads/31290-Teensey-3-2-Teensey-Loader-1-24-Issues?p=87273&viewfull=1#post87273
delay(400);
// Set all interrupts to the second-lowest priority to start with.
for (int i = 0; i < NVIC_NUM_INTERRUPTS; i++) NVIC_SET_SANE_PRIORITY(i, 0xD);
// Now set priorities for all the ones we care about. They only have meaning
// relative to each other, which means centralizing them here makes it a lot
// more manageable.
NVIC_SET_SANE_PRIORITY(IRQ_USBOTG, 0x7);
NVIC_SET_SANE_PRIORITY(IRQ_FTM0, 0xa);
NVIC_SET_SANE_PRIORITY(IRQ_PIT_CH3, 0x5);
// Builtin LED.
PERIPHERAL_BITBAND(GPIOC_PDOR, 5) = 1;
PORTC_PCR5 = PORT_PCR_DSE | PORT_PCR_SRE | PORT_PCR_MUX(1);
PERIPHERAL_BITBAND(GPIOC_PDDR, 5) = 1;
// PWM_OUT0
// FTM3_CH0
PORTD_PCR0 = PORT_PCR_DSE | PORT_PCR_MUX(4);
// PWM_OUT1
// FTM3_CH2
PORTD_PCR2 = PORT_PCR_DSE | PORT_PCR_MUX(4);
// PWM_OUT2
// FTM3_CH3
PORTD_PCR3 = PORT_PCR_DSE | PORT_PCR_MUX(4);
// PWM_OUT3
// FTM3_CH1
PORTD_PCR1 = PORT_PCR_DSE | PORT_PCR_MUX(4);
// PWM_IN0
// FTM0_CH1 (doesn't work)
// PORTC_PCR2 = PORT_PCR_MUX(4);
// PWM_IN1
// FTM0_CH0
PORTC_PCR1 = PORT_PCR_MUX(4);
// PWM_IN2
// FTM0_CH5 (doesn't work)
// PORTD_PCR5 = PORT_PCR_MUX(4);
// PWM_IN3
// FTM0_CH4
PORTD_PCR4 = PORT_PCR_MUX(4);
// PWM_IN4
// FTM0_CH2
PORTC_PCR3 = PORT_PCR_MUX(4);
// PWM_IN5
// FTM0_CH3 (doesn't work)
// PORTC_PCR4 = PORT_PCR_MUX(4);
delay(100);
PrintingParameters printing_parameters;
printing_parameters.dedicated_usb = true;
const ::std::unique_ptr<PrintingImplementation> printing =
CreatePrinting(printing_parameters);
printing->Initialize();
SIM_SCGC6 |= SIM_SCGC6_PIT;
// Workaround for errata e7914.
(void)PIT_MCR;
PIT_MCR = 0;
PIT_LDVAL3 = (BUS_CLOCK_FREQUENCY / 200) - 1;
PIT_TCTRL3 = PIT_TCTRL_TIE | PIT_TCTRL_TEN;
SetupPwmInFtm(FTM0);
SetupPwmOutFtm(FTM3);
PolyDrivetrain<float> polydrivetrain(GetDrivetrainConfig(), nullptr);
global_polydrivetrain.store(&polydrivetrain, ::std::memory_order_release);
// Leave the LED on for a bit longer.
delay(300);
printf("Done starting up\n");
// Done starting up, now turn the LED off.
PERIPHERAL_BITBAND(GPIOC_PDOR, 5) = 0;
NVIC_ENABLE_IRQ(IRQ_FTM0);
NVIC_ENABLE_IRQ(IRQ_PIT_CH3);
printf("Done starting up2\n");
while (true) {
}
return 0;
}
void __stack_chk_fail(void) {
while (true) {
GPIOC_PSOR = (1 << 5);
printf("Stack corruption detected\n");
delay(1000);
GPIOC_PCOR = (1 << 5);
delay(1000);
}
}
} // namespace motors
} // namespace frc971