/*
Copyright 2022 Benjamin Vedder benjamin@vedder.se
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see .
*/
#include "hw.h"
#include "ch.h"
#include "hal.h"
#include "stm32f4xx_conf.h"
#include "utils_math.h"
#include
#include "terminal.h"
#include "commands.h"
#include "mc_interface.h"
// Variables
typedef enum {
BT_SHUTDOWN_IDLE = 0,
BT_SHUTDOWN_HOLDING,
BT_SHUTDOWN_ARMED,
BT_SHUTDOWN_FORCE_ARMED,
BT_SHUTDOWN_REQUESTED,
} bt_shutdown_state;
static bool bt_raw_pressed = false;
static bool bt_last_raw_pressed = false;
static bool bt_pressed = false;
static bt_shutdown_state bt_state = BT_SHUTDOWN_IDLE;
static unsigned int bt_hold_counter = 0;
static unsigned int bt_debounce_counter = 0;
#define EXT_BUZZER_ON() palSetPad(HW_ICU_GPIO, HW_ICU_PIN)
#define EXT_BUZZER_OFF() palClearPad(HW_ICU_GPIO, HW_ICU_PIN)
void buzzer_init(void) {
// External Buzzer (using servo pin!)
palSetPadMode(HW_ICU_GPIO, HW_ICU_PIN,
PAL_MODE_OUTPUT_PUSHPULL |
PAL_STM32_OSPEED_HIGHEST);
EXT_BUZZER_ON();
chThdSleepMilliseconds(30);
EXT_BUZZER_OFF();
}
static void beep_off(void)
{
EXT_BUZZER_OFF();
}
static void beep_on(void)
{
EXT_BUZZER_ON();
}
// Private functions
static void terminal_button_test(int argc, const char **argv);
void hw_init_gpio(void) {
// GPIO clock enable
RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_GPIOA, ENABLE);
RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_GPIOB, ENABLE);
RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_GPIOC, ENABLE);
RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_GPIOD, ENABLE);
#ifdef HW_USE_BRK
// BRK Fault pin
palSetPadMode(BRK_GPIO, BRK_PIN, PAL_MODE_ALTERNATE(GPIO_AF_TIM1));
#else
// Soft Lockout
palSetPadMode(BRK_GPIO, BRK_PIN, PAL_MODE_INPUT);
#endif
// AUX
AUX_OFF();
palSetPadMode(AUX_GPIO, AUX_PIN,
PAL_MODE_OUTPUT_PUSHPULL |
PAL_STM32_OSPEED_HIGHEST);
// LEDs
palSetPadMode(LED_GREEN_GPIO, LED_GREEN_PIN,
PAL_MODE_OUTPUT_PUSHPULL |
PAL_STM32_OSPEED_HIGHEST);
palSetPadMode(LED_RED_GPIO, LED_RED_PIN,
PAL_MODE_OUTPUT_PUSHPULL |
PAL_STM32_OSPEED_HIGHEST);
// On-board Buzzer (using servo pin!)
palSetPadMode(HW_ICU_GPIO, HW_ICU_PIN,
PAL_MODE_OUTPUT_PUSHPULL |
PAL_STM32_OSPEED_HIGHEST);
EXT_BUZZER_OFF();
// GPIOA Configuration: Channel 1 to 3 as alternate function push-pull
palSetPadMode(GPIOA, 8, PAL_MODE_ALTERNATE(GPIO_AF_TIM1) |
PAL_STM32_OSPEED_HIGHEST |
PAL_STM32_PUDR_FLOATING);
palSetPadMode(GPIOA, 9, PAL_MODE_ALTERNATE(GPIO_AF_TIM1) |
PAL_STM32_OSPEED_HIGHEST |
PAL_STM32_PUDR_FLOATING);
palSetPadMode(GPIOA, 10, PAL_MODE_ALTERNATE(GPIO_AF_TIM1) |
PAL_STM32_OSPEED_HIGHEST |
PAL_STM32_PUDR_FLOATING);
palSetPadMode(GPIOB, 13, PAL_MODE_ALTERNATE(GPIO_AF_TIM1) |
PAL_STM32_OSPEED_HIGHEST |
PAL_STM32_PUDR_FLOATING);
palSetPadMode(GPIOB, 14, PAL_MODE_ALTERNATE(GPIO_AF_TIM1) |
PAL_STM32_OSPEED_HIGHEST |
PAL_STM32_PUDR_FLOATING);
palSetPadMode(GPIOB, 15, PAL_MODE_ALTERNATE(GPIO_AF_TIM1) |
PAL_STM32_OSPEED_HIGHEST |
PAL_STM32_PUDR_FLOATING);
// Hall sensors
palSetPadMode(HW_HALL_ENC_GPIO1, HW_HALL_ENC_PIN1, PAL_MODE_INPUT_PULLUP);
palSetPadMode(HW_HALL_ENC_GPIO2, HW_HALL_ENC_PIN2, PAL_MODE_INPUT_PULLUP);
palSetPadMode(HW_HALL_ENC_GPIO3, HW_HALL_ENC_PIN3, PAL_MODE_INPUT_PULLUP);
// Phase filters
palSetPadMode(GPIOC, 15, PAL_MODE_OUTPUT_OPENDRAIN);
palSetPadMode(GPIOC, 14, PAL_MODE_OUTPUT_OPENDRAIN);
palSetPadMode(GPIOC, 13, PAL_MODE_OUTPUT_OPENDRAIN);
PHASE_FILTER_OFF();
// ShutDown
palSetPadMode(HW_SHUTDOWN_GPIO, HW_SHUTDOWN_PIN, PAL_MODE_OUTPUT_OPENDRAIN);
palSetPadMode(HW_SHUTDOWN_SENSE_GPIO, HW_SHUTDOWN_SENSE_PIN, PAL_MODE_INPUT);
// ADC Pins
palSetPadMode(GPIOA, 0, PAL_MODE_INPUT_ANALOG);
palSetPadMode(GPIOA, 1, PAL_MODE_INPUT_ANALOG);
palSetPadMode(GPIOA, 2, PAL_MODE_INPUT_ANALOG);
palSetPadMode(GPIOA, 3, PAL_MODE_INPUT_ANALOG);
palSetPadMode(GPIOA, 5, PAL_MODE_INPUT_ANALOG);
palSetPadMode(GPIOA, 6, PAL_MODE_INPUT_ANALOG);
//palSetPadMode(GPIOB, 0, PAL_MODE_INPUT_ANALOG);
palSetPadMode(GPIOB, 1, PAL_MODE_INPUT_ANALOG);
palSetPadMode(GPIOC, 0, PAL_MODE_INPUT_ANALOG);
palSetPadMode(GPIOC, 1, PAL_MODE_INPUT_ANALOG);
palSetPadMode(GPIOC, 2, PAL_MODE_INPUT_ANALOG);
palSetPadMode(GPIOC, 3, PAL_MODE_INPUT_ANALOG);
palSetPadMode(GPIOC, 4, PAL_MODE_INPUT_ANALOG);
terminal_register_command_callback(
"test_button",
"Try sampling the shutdown button",
0,
terminal_button_test);
}
void hw_setup_adc_channels(void) {
// ADC1 regular channels
ADC_RegularChannelConfig(ADC1, ADC_Channel_10, 1, ADC_SampleTime_15Cycles);
ADC_RegularChannelConfig(ADC1, ADC_Channel_0, 2, ADC_SampleTime_15Cycles);
ADC_RegularChannelConfig(ADC1, ADC_Channel_5, 3, ADC_SampleTime_15Cycles);
ADC_RegularChannelConfig(ADC1, ADC_Channel_14, 4, ADC_SampleTime_15Cycles);
ADC_RegularChannelConfig(ADC1, ADC_Channel_Vrefint, 5, ADC_SampleTime_15Cycles);
ADC_RegularChannelConfig(ADC1, ADC_Channel_8, 6, ADC_SampleTime_15Cycles);
// ADC2 regular channels
ADC_RegularChannelConfig(ADC2, ADC_Channel_11, 1, ADC_SampleTime_15Cycles);
ADC_RegularChannelConfig(ADC2, ADC_Channel_1, 2, ADC_SampleTime_15Cycles);
ADC_RegularChannelConfig(ADC2, ADC_Channel_6, 3, ADC_SampleTime_15Cycles);
ADC_RegularChannelConfig(ADC2, ADC_Channel_15, 4, ADC_SampleTime_15Cycles);
ADC_RegularChannelConfig(ADC2, ADC_Channel_0, 5, ADC_SampleTime_15Cycles);
ADC_RegularChannelConfig(ADC2, ADC_Channel_9, 6, ADC_SampleTime_15Cycles);
// ADC3 regular channels
ADC_RegularChannelConfig(ADC3, ADC_Channel_12, 1, ADC_SampleTime_15Cycles);
ADC_RegularChannelConfig(ADC3, ADC_Channel_2, 2, ADC_SampleTime_15Cycles);
ADC_RegularChannelConfig(ADC3, ADC_Channel_3, 3, ADC_SampleTime_15Cycles);
ADC_RegularChannelConfig(ADC3, ADC_Channel_13, 4, ADC_SampleTime_15Cycles);
ADC_RegularChannelConfig(ADC3, ADC_Channel_1, 5, ADC_SampleTime_15Cycles);
ADC_RegularChannelConfig(ADC3, ADC_Channel_2, 6, ADC_SampleTime_15Cycles);
// Injected channels
ADC_InjectedChannelConfig(ADC1, ADC_Channel_10, 1, ADC_SampleTime_15Cycles);
ADC_InjectedChannelConfig(ADC2, ADC_Channel_11, 1, ADC_SampleTime_15Cycles);
ADC_InjectedChannelConfig(ADC3, ADC_Channel_12, 1, ADC_SampleTime_15Cycles);
ADC_InjectedChannelConfig(ADC1, ADC_Channel_10, 2, ADC_SampleTime_15Cycles);
ADC_InjectedChannelConfig(ADC2, ADC_Channel_11, 2, ADC_SampleTime_15Cycles);
ADC_InjectedChannelConfig(ADC3, ADC_Channel_12, 2, ADC_SampleTime_15Cycles);
ADC_InjectedChannelConfig(ADC1, ADC_Channel_10, 3, ADC_SampleTime_15Cycles);
ADC_InjectedChannelConfig(ADC2, ADC_Channel_11, 3, ADC_SampleTime_15Cycles);
ADC_InjectedChannelConfig(ADC3, ADC_Channel_12, 3, ADC_SampleTime_15Cycles);
}
// Generic hardware I2C is not routed on JetFleetF6: HW_I2C_SDA shares PC5 with
// shutdown sense, and PC5 has no valid I2C alternate function on STM32F4. The
// IMU has its own bit-banged path via LSM6DS3_SDA_*, so these stay no-ops.
void hw_start_i2c(void) {
}
void hw_stop_i2c(void) {
}
void hw_try_restore_i2c(void) {
}
#define TIME_500MS 50
#define TIME_3S 300
#define ERPM_THRESHOLD 100
#define BUTTON_DEBOUNCE_SAMPLES 2
static bool shutdown_button_pressed(void) {
bt_raw_pressed = palReadPad(HW_SHUTDOWN_SENSE_GPIO, HW_SHUTDOWN_SENSE_PIN) == PAL_HIGH;
if (bt_raw_pressed == bt_last_raw_pressed) {
if (bt_debounce_counter < BUTTON_DEBOUNCE_SAMPLES) {
bt_debounce_counter++;
}
} else {
bt_last_raw_pressed = bt_raw_pressed;
bt_debounce_counter = 1;
}
if (bt_debounce_counter >= BUTTON_DEBOUNCE_SAMPLES) {
bt_pressed = bt_raw_pressed;
}
return bt_pressed;
}
/**
* hw_sample_shutdown_button - return false if shutdown is requested, true otherwise
*
* Behavior: sample the shutdown button as a digital input. The input is expected
* to be low when released and high when pressed.
*
* Once the hold counter reaches the threshold, provided that the ERPM is below
* 100, a very short (20ms) beep will go off. Shutdown actually happens on the
* release edge when the press is over.
*
* If the motor is spinning faster, then a 3s press is required. Buzzer will beep once the
* time has been reached. Again, shutdown happens on release.
*
* Normal shutdown time: 0.5s
* Emergency shutdown time: 3.0s
*/
bool hw_sample_shutdown_button(void) {
bool pressed = shutdown_button_pressed();
switch (bt_state) {
case BT_SHUTDOWN_IDLE:
if (pressed) {
bt_hold_counter = 1;
bt_state = BT_SHUTDOWN_HOLDING;
} else {
bt_hold_counter = 0;
}
break;
case BT_SHUTDOWN_HOLDING:
if (!pressed) {
bt_hold_counter = 0;
beep_off();
bt_state = BT_SHUTDOWN_IDLE;
break;
}
bt_hold_counter++;
if (bt_hold_counter > TIME_500MS) {
if (fabsf(mc_interface_get_rpm()) < ERPM_THRESHOLD) {
// Power-down is triggered by releasing the button after arming.
bt_hold_counter = 0;
bt_state = BT_SHUTDOWN_ARMED;
beep_on();
chThdSleepMilliseconds(20);
beep_off();
} else if (bt_hold_counter > TIME_3S) {
// Emergency Power-Down - beep to let the user know it's ready.
beep_on();
bt_hold_counter = 0;
bt_state = BT_SHUTDOWN_FORCE_ARMED;
}
}
break;
case BT_SHUTDOWN_ARMED:
if (fabsf(mc_interface_get_rpm()) > ERPM_THRESHOLD) {
bt_hold_counter = 0;
beep_off();
bt_state = BT_SHUTDOWN_IDLE;
break;
}
if (!pressed) {
beep_off();
bt_state = BT_SHUTDOWN_REQUESTED;
return false;
}
break;
case BT_SHUTDOWN_FORCE_ARMED:
if (!pressed) {
beep_off();
bt_state = BT_SHUTDOWN_REQUESTED;
return false;
}
break;
case BT_SHUTDOWN_REQUESTED:
beep_off();
return false;
default:
bt_hold_counter = 0;
beep_off();
bt_state = BT_SHUTDOWN_IDLE;
break;
}
return true;
}
float hw_JetFleet_get_temp(void) {
float t1 = (1.0 / ((logf(NTC_RES(ADC_Value[ADC_IND_TEMP_MOS]) / 10000.0) / 3380.0) + (1.0 / 298.15)) - 273.15);
float t3 = (1.0 / ((logf(NTC_RES(ADC_Value[ADC_IND_TEMP_MOS_3]) / 10000.0) / 3380.0) + (1.0 / 298.15)) - 273.15);
float res = 0.0;
if (t1 >= t3) {
res = t1;
} else {
res = t3;
}
return res;
}
static void terminal_button_test(int argc, const char **argv) {
(void)argc;
(void)argv;
for (int i = 0;i < 40;i++) {
commands_printf("BT: %d:%d raw=%d pressed=%d state=%d RPM=%.1f",
HW_SAMPLE_SHUTDOWN(), bt_hold_counter, (int)bt_raw_pressed,
(int)bt_pressed, (int)bt_state, (double)mc_interface_get_rpm());
chThdSleepMilliseconds(100);
}
}