Compile FrogPilot
This commit is contained in:
@@ -1,18 +0,0 @@
|
||||
import os
|
||||
import copy
|
||||
|
||||
Import('build_project', 'base_project_f4', 'base_project_h7')
|
||||
|
||||
build_projects = {
|
||||
"panda_jungle": base_project_f4,
|
||||
"panda_jungle_h7": base_project_h7,
|
||||
}
|
||||
|
||||
for project_name, project in build_projects.items():
|
||||
flags = [
|
||||
"-DPANDA_JUNGLE",
|
||||
]
|
||||
if os.getenv("FINAL_PROVISIONING"):
|
||||
flags += ["-DFINAL_PROVISIONING"]
|
||||
|
||||
build_project(project_name, project, flags)
|
||||
@@ -1,82 +0,0 @@
|
||||
// ******************** Prototypes ********************
|
||||
typedef void (*board_init)(void);
|
||||
typedef void (*board_set_led)(uint8_t color, bool enabled);
|
||||
typedef void (*board_board_tick)(void);
|
||||
typedef bool (*board_get_button)(void);
|
||||
typedef void (*board_set_panda_power)(bool enabled);
|
||||
typedef void (*board_set_panda_individual_power)(uint8_t port_num, bool enabled);
|
||||
typedef void (*board_set_ignition)(bool enabled);
|
||||
typedef void (*board_set_individual_ignition)(uint8_t bitmask);
|
||||
typedef void (*board_set_harness_orientation)(uint8_t orientation);
|
||||
typedef void (*board_set_can_mode)(uint8_t mode);
|
||||
typedef void (*board_enable_can_transciever)(uint8_t transciever, bool enabled);
|
||||
typedef void (*board_enable_header_pin)(uint8_t pin_num, bool enabled);
|
||||
typedef float (*board_get_channel_power)(uint8_t channel);
|
||||
typedef uint16_t (*board_get_sbu_mV)(uint8_t channel, uint8_t sbu);
|
||||
|
||||
struct board {
|
||||
const bool has_canfd;
|
||||
const bool has_sbu_sense;
|
||||
const uint16_t avdd_mV;
|
||||
board_init init;
|
||||
board_set_led set_led;
|
||||
board_board_tick board_tick;
|
||||
board_get_button get_button;
|
||||
board_set_panda_power set_panda_power;
|
||||
board_set_panda_individual_power set_panda_individual_power;
|
||||
board_set_ignition set_ignition;
|
||||
board_set_individual_ignition set_individual_ignition;
|
||||
board_set_harness_orientation set_harness_orientation;
|
||||
board_set_can_mode set_can_mode;
|
||||
board_enable_can_transciever enable_can_transciever;
|
||||
board_enable_header_pin enable_header_pin;
|
||||
board_get_channel_power get_channel_power;
|
||||
board_get_sbu_mV get_sbu_mV;
|
||||
|
||||
// TODO: shouldn't need these
|
||||
bool has_spi;
|
||||
};
|
||||
|
||||
// ******************* Definitions ********************
|
||||
#define HW_TYPE_UNKNOWN 0U
|
||||
#define HW_TYPE_V1 1U
|
||||
#define HW_TYPE_V2 2U
|
||||
|
||||
// LED colors
|
||||
#define LED_RED 0U
|
||||
#define LED_GREEN 1U
|
||||
#define LED_BLUE 2U
|
||||
|
||||
// CAN modes
|
||||
#define CAN_MODE_NORMAL 0U
|
||||
#define CAN_MODE_GMLAN_CAN2 1U
|
||||
#define CAN_MODE_GMLAN_CAN3 2U
|
||||
#define CAN_MODE_OBD_CAN2 3U
|
||||
|
||||
// Harness states
|
||||
#define HARNESS_ORIENTATION_NONE 0U
|
||||
#define HARNESS_ORIENTATION_1 1U
|
||||
#define HARNESS_ORIENTATION_2 2U
|
||||
|
||||
#define SBU1 0U
|
||||
#define SBU2 1U
|
||||
|
||||
// ********************* Globals **********************
|
||||
uint8_t harness_orientation = HARNESS_ORIENTATION_NONE;
|
||||
uint8_t can_mode = CAN_MODE_NORMAL;
|
||||
uint8_t ignition = 0U;
|
||||
|
||||
|
||||
void unused_set_individual_ignition(uint8_t bitmask) {
|
||||
UNUSED(bitmask);
|
||||
}
|
||||
|
||||
void unused_board_enable_header_pin(uint8_t pin_num, bool enabled) {
|
||||
UNUSED(pin_num);
|
||||
UNUSED(enabled);
|
||||
}
|
||||
|
||||
void unused_set_panda_individual_power(uint8_t port_num, bool enabled) {
|
||||
UNUSED(port_num);
|
||||
UNUSED(enabled);
|
||||
}
|
||||
@@ -1,178 +0,0 @@
|
||||
// ///////////////////////// //
|
||||
// Jungle board v1 (STM32F4) //
|
||||
// ///////////////////////// //
|
||||
|
||||
void board_v1_set_led(uint8_t color, bool enabled) {
|
||||
switch (color) {
|
||||
case LED_RED:
|
||||
set_gpio_output(GPIOC, 9, !enabled);
|
||||
break;
|
||||
case LED_GREEN:
|
||||
set_gpio_output(GPIOC, 7, !enabled);
|
||||
break;
|
||||
case LED_BLUE:
|
||||
set_gpio_output(GPIOC, 6, !enabled);
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
void board_v1_enable_can_transciever(uint8_t transciever, bool enabled) {
|
||||
switch (transciever) {
|
||||
case 1U:
|
||||
set_gpio_output(GPIOC, 1, !enabled);
|
||||
break;
|
||||
case 2U:
|
||||
set_gpio_output(GPIOC, 13, !enabled);
|
||||
break;
|
||||
case 3U:
|
||||
set_gpio_output(GPIOA, 0, !enabled);
|
||||
break;
|
||||
case 4U:
|
||||
set_gpio_output(GPIOB, 10, !enabled);
|
||||
break;
|
||||
default:
|
||||
print("Invalid CAN transciever ("); puth(transciever); print("): enabling failed\n");
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
void board_v1_set_can_mode(uint8_t mode) {
|
||||
board_v1_enable_can_transciever(2U, false);
|
||||
board_v1_enable_can_transciever(4U, false);
|
||||
switch (mode) {
|
||||
case CAN_MODE_NORMAL:
|
||||
print("Setting normal CAN mode\n");
|
||||
// B12,B13: disable OBD mode
|
||||
set_gpio_mode(GPIOB, 12, MODE_INPUT);
|
||||
set_gpio_mode(GPIOB, 13, MODE_INPUT);
|
||||
|
||||
// B5,B6: normal CAN2 mode
|
||||
set_gpio_alternate(GPIOB, 5, GPIO_AF9_CAN2);
|
||||
set_gpio_alternate(GPIOB, 6, GPIO_AF9_CAN2);
|
||||
can_mode = CAN_MODE_NORMAL;
|
||||
board_v1_enable_can_transciever(2U, true);
|
||||
break;
|
||||
case CAN_MODE_OBD_CAN2:
|
||||
print("Setting OBD CAN mode\n");
|
||||
// B5,B6: disable normal CAN2 mode
|
||||
set_gpio_mode(GPIOB, 5, MODE_INPUT);
|
||||
set_gpio_mode(GPIOB, 6, MODE_INPUT);
|
||||
|
||||
// B12,B13: OBD mode
|
||||
set_gpio_alternate(GPIOB, 12, GPIO_AF9_CAN2);
|
||||
set_gpio_alternate(GPIOB, 13, GPIO_AF9_CAN2);
|
||||
can_mode = CAN_MODE_OBD_CAN2;
|
||||
board_v1_enable_can_transciever(4U, true);
|
||||
break;
|
||||
default:
|
||||
print("Tried to set unsupported CAN mode: "); puth(mode); print("\n");
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
void board_v1_set_harness_orientation(uint8_t orientation) {
|
||||
switch (orientation) {
|
||||
case HARNESS_ORIENTATION_NONE:
|
||||
set_gpio_output(GPIOA, 2, false);
|
||||
set_gpio_output(GPIOA, 3, false);
|
||||
set_gpio_output(GPIOA, 4, false);
|
||||
set_gpio_output(GPIOA, 5, false);
|
||||
harness_orientation = orientation;
|
||||
break;
|
||||
case HARNESS_ORIENTATION_1:
|
||||
set_gpio_output(GPIOA, 2, false);
|
||||
set_gpio_output(GPIOA, 3, (ignition != 0U));
|
||||
set_gpio_output(GPIOA, 4, true);
|
||||
set_gpio_output(GPIOA, 5, false);
|
||||
harness_orientation = orientation;
|
||||
break;
|
||||
case HARNESS_ORIENTATION_2:
|
||||
set_gpio_output(GPIOA, 2, (ignition != 0U));
|
||||
set_gpio_output(GPIOA, 3, false);
|
||||
set_gpio_output(GPIOA, 4, false);
|
||||
set_gpio_output(GPIOA, 5, true);
|
||||
harness_orientation = orientation;
|
||||
break;
|
||||
default:
|
||||
print("Tried to set an unsupported harness orientation: "); puth(orientation); print("\n");
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
bool panda_power = false;
|
||||
void board_v1_set_panda_power(bool enable) {
|
||||
panda_power = enable;
|
||||
set_gpio_output(GPIOB, 14, enable);
|
||||
}
|
||||
|
||||
bool board_v1_get_button(void) {
|
||||
return get_gpio_input(GPIOC, 8);
|
||||
}
|
||||
|
||||
void board_v1_set_ignition(bool enabled) {
|
||||
ignition = enabled ? 0xFFU : 0U;
|
||||
board_v1_set_harness_orientation(harness_orientation);
|
||||
}
|
||||
|
||||
float board_v1_get_channel_power(uint8_t channel) {
|
||||
UNUSED(channel);
|
||||
return 0.0f;
|
||||
}
|
||||
|
||||
uint16_t board_v1_get_sbu_mV(uint8_t channel, uint8_t sbu) {
|
||||
UNUSED(channel); UNUSED(sbu);
|
||||
return 0U;
|
||||
}
|
||||
|
||||
void board_v1_init(void) {
|
||||
common_init_gpio();
|
||||
|
||||
// A8,A15: normal CAN3 mode
|
||||
set_gpio_alternate(GPIOA, 8, GPIO_AF11_CAN3);
|
||||
set_gpio_alternate(GPIOA, 15, GPIO_AF11_CAN3);
|
||||
|
||||
board_v1_set_can_mode(CAN_MODE_NORMAL);
|
||||
|
||||
// Enable CAN transcievers
|
||||
for(uint8_t i = 1; i <= 4; i++) {
|
||||
board_v1_enable_can_transciever(i, true);
|
||||
}
|
||||
|
||||
// Disable LEDs
|
||||
board_v1_set_led(LED_RED, false);
|
||||
board_v1_set_led(LED_GREEN, false);
|
||||
board_v1_set_led(LED_BLUE, false);
|
||||
|
||||
// Set normal CAN mode
|
||||
board_v1_set_can_mode(CAN_MODE_NORMAL);
|
||||
|
||||
// Set to no harness orientation
|
||||
board_v1_set_harness_orientation(HARNESS_ORIENTATION_NONE);
|
||||
|
||||
// Enable panda power by default
|
||||
board_v1_set_panda_power(true);
|
||||
}
|
||||
|
||||
void board_v1_tick(void) {}
|
||||
|
||||
const board board_v1 = {
|
||||
.has_canfd = false,
|
||||
.has_sbu_sense = false,
|
||||
.avdd_mV = 3300U,
|
||||
.init = &board_v1_init,
|
||||
.set_led = &board_v1_set_led,
|
||||
.board_tick = &board_v1_tick,
|
||||
.get_button = &board_v1_get_button,
|
||||
.set_panda_power = &board_v1_set_panda_power,
|
||||
.set_panda_individual_power = &unused_set_panda_individual_power,
|
||||
.set_ignition = &board_v1_set_ignition,
|
||||
.set_individual_ignition = &unused_set_individual_ignition,
|
||||
.set_harness_orientation = &board_v1_set_harness_orientation,
|
||||
.set_can_mode = &board_v1_set_can_mode,
|
||||
.enable_can_transciever = &board_v1_enable_can_transciever,
|
||||
.enable_header_pin = &unused_board_enable_header_pin,
|
||||
.get_channel_power = &board_v1_get_channel_power,
|
||||
.get_sbu_mV = &board_v1_get_sbu_mV,
|
||||
};
|
||||
@@ -1,328 +0,0 @@
|
||||
// ///////////////////////// //
|
||||
// Jungle board v2 (STM32H7) //
|
||||
// ///////////////////////// //
|
||||
|
||||
const gpio_t power_pins[] = {
|
||||
{.bank = GPIOA, .pin = 0},
|
||||
{.bank = GPIOA, .pin = 1},
|
||||
{.bank = GPIOF, .pin = 12},
|
||||
{.bank = GPIOA, .pin = 5},
|
||||
{.bank = GPIOC, .pin = 5},
|
||||
{.bank = GPIOB, .pin = 2},
|
||||
};
|
||||
|
||||
const gpio_t sbu1_ignition_pins[] = {
|
||||
{.bank = GPIOD, .pin = 0},
|
||||
{.bank = GPIOD, .pin = 5},
|
||||
{.bank = GPIOD, .pin = 12},
|
||||
{.bank = GPIOD, .pin = 14},
|
||||
{.bank = GPIOE, .pin = 5},
|
||||
{.bank = GPIOE, .pin = 9},
|
||||
};
|
||||
|
||||
const gpio_t sbu1_relay_pins[] = {
|
||||
{.bank = GPIOD, .pin = 1},
|
||||
{.bank = GPIOD, .pin = 6},
|
||||
{.bank = GPIOD, .pin = 11},
|
||||
{.bank = GPIOD, .pin = 15},
|
||||
{.bank = GPIOE, .pin = 6},
|
||||
{.bank = GPIOE, .pin = 10},
|
||||
};
|
||||
|
||||
const gpio_t sbu2_ignition_pins[] = {
|
||||
{.bank = GPIOD, .pin = 3},
|
||||
{.bank = GPIOD, .pin = 8},
|
||||
{.bank = GPIOD, .pin = 9},
|
||||
{.bank = GPIOE, .pin = 0},
|
||||
{.bank = GPIOE, .pin = 7},
|
||||
{.bank = GPIOE, .pin = 11},
|
||||
};
|
||||
|
||||
const gpio_t sbu2_relay_pins[] = {
|
||||
{.bank = GPIOD, .pin = 4},
|
||||
{.bank = GPIOD, .pin = 10},
|
||||
{.bank = GPIOD, .pin = 13},
|
||||
{.bank = GPIOE, .pin = 1},
|
||||
{.bank = GPIOE, .pin = 8},
|
||||
{.bank = GPIOE, .pin = 12},
|
||||
};
|
||||
|
||||
const adc_channel_t sbu1_channels[] = {
|
||||
{.adc = ADC3, .channel = 12},
|
||||
{.adc = ADC3, .channel = 2},
|
||||
{.adc = ADC3, .channel = 4},
|
||||
{.adc = ADC3, .channel = 6},
|
||||
{.adc = ADC3, .channel = 8},
|
||||
{.adc = ADC3, .channel = 10},
|
||||
};
|
||||
|
||||
const adc_channel_t sbu2_channels[] = {
|
||||
{.adc = ADC1, .channel = 13},
|
||||
{.adc = ADC3, .channel = 3},
|
||||
{.adc = ADC3, .channel = 5},
|
||||
{.adc = ADC3, .channel = 7},
|
||||
{.adc = ADC3, .channel = 9},
|
||||
{.adc = ADC3, .channel = 11},
|
||||
};
|
||||
|
||||
void board_v2_set_led(uint8_t color, bool enabled) {
|
||||
switch (color) {
|
||||
case LED_RED:
|
||||
set_gpio_output(GPIOE, 4, !enabled);
|
||||
break;
|
||||
case LED_GREEN:
|
||||
set_gpio_output(GPIOE, 3, !enabled);
|
||||
break;
|
||||
case LED_BLUE:
|
||||
set_gpio_output(GPIOE, 2, !enabled);
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
void board_v2_set_harness_orientation(uint8_t orientation) {
|
||||
switch (orientation) {
|
||||
case HARNESS_ORIENTATION_NONE:
|
||||
gpio_set_all_output(sbu1_ignition_pins, sizeof(sbu1_ignition_pins) / sizeof(gpio_t), false);
|
||||
gpio_set_all_output(sbu1_relay_pins, sizeof(sbu1_relay_pins) / sizeof(gpio_t), false);
|
||||
gpio_set_all_output(sbu2_ignition_pins, sizeof(sbu2_ignition_pins) / sizeof(gpio_t), false);
|
||||
gpio_set_all_output(sbu2_relay_pins, sizeof(sbu2_relay_pins) / sizeof(gpio_t), false);
|
||||
harness_orientation = orientation;
|
||||
break;
|
||||
case HARNESS_ORIENTATION_1:
|
||||
gpio_set_all_output(sbu1_ignition_pins, sizeof(sbu1_ignition_pins) / sizeof(gpio_t), false);
|
||||
gpio_set_all_output(sbu1_relay_pins, sizeof(sbu1_relay_pins) / sizeof(gpio_t), true);
|
||||
gpio_set_bitmask(sbu2_ignition_pins, sizeof(sbu2_ignition_pins) / sizeof(gpio_t), ignition);
|
||||
gpio_set_all_output(sbu2_relay_pins, sizeof(sbu2_relay_pins) / sizeof(gpio_t), false);
|
||||
harness_orientation = orientation;
|
||||
break;
|
||||
case HARNESS_ORIENTATION_2:
|
||||
gpio_set_bitmask(sbu1_ignition_pins, sizeof(sbu1_ignition_pins) / sizeof(gpio_t), ignition);
|
||||
gpio_set_all_output(sbu1_relay_pins, sizeof(sbu1_relay_pins) / sizeof(gpio_t), false);
|
||||
gpio_set_all_output(sbu2_ignition_pins, sizeof(sbu2_ignition_pins) / sizeof(gpio_t), false);
|
||||
gpio_set_all_output(sbu2_relay_pins, sizeof(sbu2_relay_pins) / sizeof(gpio_t), true);
|
||||
harness_orientation = orientation;
|
||||
break;
|
||||
default:
|
||||
print("Tried to set an unsupported harness orientation: "); puth(orientation); print("\n");
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
void board_v2_enable_can_transciever(uint8_t transciever, bool enabled) {
|
||||
switch (transciever) {
|
||||
case 1U:
|
||||
set_gpio_output(GPIOG, 11, !enabled);
|
||||
break;
|
||||
case 2U:
|
||||
set_gpio_output(GPIOB, 3, !enabled);
|
||||
break;
|
||||
case 3U:
|
||||
set_gpio_output(GPIOD, 7, !enabled);
|
||||
break;
|
||||
case 4U:
|
||||
set_gpio_output(GPIOB, 4, !enabled);
|
||||
break;
|
||||
default:
|
||||
print("Invalid CAN transciever ("); puth(transciever); print("): enabling failed\n");
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
void board_v2_enable_header_pin(uint8_t pin_num, bool enabled) {
|
||||
if (pin_num < 8U) {
|
||||
set_gpio_output(GPIOG, pin_num, enabled);
|
||||
} else {
|
||||
print("Invalid pin number ("); puth(pin_num); print("): enabling failed\n");
|
||||
}
|
||||
}
|
||||
|
||||
void board_v2_set_can_mode(uint8_t mode) {
|
||||
board_v2_enable_can_transciever(2U, false);
|
||||
board_v2_enable_can_transciever(4U, false);
|
||||
switch (mode) {
|
||||
case CAN_MODE_NORMAL:
|
||||
// B12,B13: disable normal mode
|
||||
set_gpio_pullup(GPIOB, 12, PULL_NONE);
|
||||
set_gpio_mode(GPIOB, 12, MODE_ANALOG);
|
||||
|
||||
set_gpio_pullup(GPIOB, 13, PULL_NONE);
|
||||
set_gpio_mode(GPIOB, 13, MODE_ANALOG);
|
||||
|
||||
// B5,B6: FDCAN2 mode
|
||||
set_gpio_pullup(GPIOB, 5, PULL_NONE);
|
||||
set_gpio_alternate(GPIOB, 5, GPIO_AF9_FDCAN2);
|
||||
|
||||
set_gpio_pullup(GPIOB, 6, PULL_NONE);
|
||||
set_gpio_alternate(GPIOB, 6, GPIO_AF9_FDCAN2);
|
||||
can_mode = CAN_MODE_NORMAL;
|
||||
board_v2_enable_can_transciever(2U, true);
|
||||
break;
|
||||
case CAN_MODE_OBD_CAN2:
|
||||
// B5,B6: disable normal mode
|
||||
set_gpio_pullup(GPIOB, 5, PULL_NONE);
|
||||
set_gpio_mode(GPIOB, 5, MODE_ANALOG);
|
||||
|
||||
set_gpio_pullup(GPIOB, 6, PULL_NONE);
|
||||
set_gpio_mode(GPIOB, 6, MODE_ANALOG);
|
||||
// B12,B13: FDCAN2 mode
|
||||
set_gpio_pullup(GPIOB, 12, PULL_NONE);
|
||||
set_gpio_alternate(GPIOB, 12, GPIO_AF9_FDCAN2);
|
||||
|
||||
set_gpio_pullup(GPIOB, 13, PULL_NONE);
|
||||
set_gpio_alternate(GPIOB, 13, GPIO_AF9_FDCAN2);
|
||||
can_mode = CAN_MODE_OBD_CAN2;
|
||||
board_v2_enable_can_transciever(4U, true);
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
bool panda_power = false;
|
||||
uint8_t panda_power_bitmask = 0U;
|
||||
void board_v2_set_panda_power(bool enable) {
|
||||
panda_power = enable;
|
||||
gpio_set_all_output(power_pins, sizeof(power_pins) / sizeof(gpio_t), enable);
|
||||
if (enable) {
|
||||
panda_power_bitmask = 0xFFU;
|
||||
} else {
|
||||
panda_power_bitmask = 0U;
|
||||
}
|
||||
}
|
||||
|
||||
void board_v2_set_panda_individual_power(uint8_t port_num, bool enable) {
|
||||
port_num -= 1U;
|
||||
if (port_num < 6U) {
|
||||
panda_power_bitmask &= ~(1U << port_num);
|
||||
panda_power_bitmask |= (enable ? 1U : 0U) << port_num;
|
||||
} else {
|
||||
print("Invalid port number ("); puth(port_num); print("): enabling failed\n");
|
||||
}
|
||||
gpio_set_bitmask(power_pins, sizeof(power_pins) / sizeof(gpio_t), (uint32_t)panda_power_bitmask);
|
||||
}
|
||||
|
||||
bool board_v2_get_button(void) {
|
||||
return get_gpio_input(GPIOG, 15);
|
||||
}
|
||||
|
||||
void board_v2_set_ignition(bool enabled) {
|
||||
ignition = enabled ? 0xFFU : 0U;
|
||||
board_v2_set_harness_orientation(harness_orientation);
|
||||
}
|
||||
|
||||
void board_v2_set_individual_ignition(uint8_t bitmask) {
|
||||
ignition = bitmask;
|
||||
board_v2_set_harness_orientation(harness_orientation);
|
||||
}
|
||||
|
||||
float board_v2_get_channel_power(uint8_t channel) {
|
||||
float ret = 0.0f;
|
||||
if ((channel >= 1U) && (channel <= 6U)) {
|
||||
uint16_t readout = adc_get_mV(ADC1, channel - 1U); // these are mapped nicely in hardware
|
||||
|
||||
ret = (((float) readout / 33e6) - 0.8e-6) / 52e-6 * 12.0f;
|
||||
} else {
|
||||
print("Invalid channel ("); puth(channel); print(")\n");
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
uint16_t board_v2_get_sbu_mV(uint8_t channel, uint8_t sbu) {
|
||||
uint16_t ret = 0U;
|
||||
if ((channel >= 1U) && (channel <= 6U)) {
|
||||
switch(sbu){
|
||||
case SBU1:
|
||||
ret = adc_get_mV(sbu1_channels[channel - 1U].adc, sbu1_channels[channel - 1U].channel);
|
||||
break;
|
||||
case SBU2:
|
||||
ret = adc_get_mV(sbu2_channels[channel - 1U].adc, sbu2_channels[channel - 1U].channel);
|
||||
break;
|
||||
default:
|
||||
print("Invalid SBU ("); puth(sbu); print(")\n");
|
||||
break;
|
||||
}
|
||||
} else {
|
||||
print("Invalid channel ("); puth(channel); print(")\n");
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
void board_v2_init(void) {
|
||||
common_init_gpio();
|
||||
|
||||
// Disable LEDs
|
||||
board_v2_set_led(LED_RED, false);
|
||||
board_v2_set_led(LED_GREEN, false);
|
||||
board_v2_set_led(LED_BLUE, false);
|
||||
|
||||
// Normal CAN mode
|
||||
board_v2_set_can_mode(CAN_MODE_NORMAL);
|
||||
|
||||
// Enable CAN transcievers
|
||||
for(uint8_t i = 1; i <= 4; i++) {
|
||||
board_v2_enable_can_transciever(i, true);
|
||||
}
|
||||
|
||||
// Set to no harness orientation
|
||||
board_v2_set_harness_orientation(HARNESS_ORIENTATION_NONE);
|
||||
|
||||
// Enable panda power by default
|
||||
board_v2_set_panda_power(true);
|
||||
|
||||
// Current monitor channels
|
||||
adc_init(ADC1);
|
||||
register_set_bits(&SYSCFG->PMCR, SYSCFG_PMCR_PA0SO | SYSCFG_PMCR_PA1SO); // open up analog switches for PA0_C and PA1_C
|
||||
set_gpio_mode(GPIOF, 11, MODE_ANALOG);
|
||||
set_gpio_mode(GPIOA, 6, MODE_ANALOG);
|
||||
set_gpio_mode(GPIOC, 4, MODE_ANALOG);
|
||||
set_gpio_mode(GPIOB, 1, MODE_ANALOG);
|
||||
|
||||
// SBU channels
|
||||
adc_init(ADC3);
|
||||
set_gpio_mode(GPIOC, 2, MODE_ANALOG);
|
||||
set_gpio_mode(GPIOC, 3, MODE_ANALOG);
|
||||
set_gpio_mode(GPIOF, 9, MODE_ANALOG);
|
||||
set_gpio_mode(GPIOF, 7, MODE_ANALOG);
|
||||
set_gpio_mode(GPIOF, 5, MODE_ANALOG);
|
||||
set_gpio_mode(GPIOF, 3, MODE_ANALOG);
|
||||
set_gpio_mode(GPIOF, 10, MODE_ANALOG);
|
||||
set_gpio_mode(GPIOF, 8, MODE_ANALOG);
|
||||
set_gpio_mode(GPIOF, 6, MODE_ANALOG);
|
||||
set_gpio_mode(GPIOF, 4, MODE_ANALOG);
|
||||
set_gpio_mode(GPIOC, 0, MODE_ANALOG);
|
||||
set_gpio_mode(GPIOC, 1, MODE_ANALOG);
|
||||
|
||||
// Header pins
|
||||
set_gpio_mode(GPIOG, 0, MODE_OUTPUT);
|
||||
set_gpio_mode(GPIOG, 1, MODE_OUTPUT);
|
||||
set_gpio_mode(GPIOG, 2, MODE_OUTPUT);
|
||||
set_gpio_mode(GPIOG, 3, MODE_OUTPUT);
|
||||
set_gpio_mode(GPIOG, 4, MODE_OUTPUT);
|
||||
set_gpio_mode(GPIOG, 5, MODE_OUTPUT);
|
||||
set_gpio_mode(GPIOG, 6, MODE_OUTPUT);
|
||||
set_gpio_mode(GPIOG, 7, MODE_OUTPUT);
|
||||
}
|
||||
|
||||
void board_v2_tick(void) {}
|
||||
|
||||
const board board_v2 = {
|
||||
.has_canfd = true,
|
||||
.has_sbu_sense = true,
|
||||
.avdd_mV = 3300U,
|
||||
.init = &board_v2_init,
|
||||
.set_led = &board_v2_set_led,
|
||||
.board_tick = &board_v2_tick,
|
||||
.get_button = &board_v2_get_button,
|
||||
.set_panda_power = &board_v2_set_panda_power,
|
||||
.set_panda_individual_power = &board_v2_set_panda_individual_power,
|
||||
.set_ignition = &board_v2_set_ignition,
|
||||
.set_individual_ignition = &board_v2_set_individual_ignition,
|
||||
.set_harness_orientation = &board_v2_set_harness_orientation,
|
||||
.set_can_mode = &board_v2_set_can_mode,
|
||||
.enable_can_transciever = &board_v2_enable_can_transciever,
|
||||
.enable_header_pin = &board_v2_enable_header_pin,
|
||||
.get_channel_power = &board_v2_get_channel_power,
|
||||
.get_sbu_mV = &board_v2_get_sbu_mV,
|
||||
};
|
||||
@@ -1,24 +0,0 @@
|
||||
// When changing these structs, python/__init__.py needs to be kept up to date!
|
||||
|
||||
#define JUNGLE_HEALTH_PACKET_VERSION 1
|
||||
struct __attribute__((packed)) jungle_health_t {
|
||||
uint32_t uptime_pkt;
|
||||
float ch1_power;
|
||||
float ch2_power;
|
||||
float ch3_power;
|
||||
float ch4_power;
|
||||
float ch5_power;
|
||||
float ch6_power;
|
||||
uint16_t ch1_sbu1_mV;
|
||||
uint16_t ch1_sbu2_mV;
|
||||
uint16_t ch2_sbu1_mV;
|
||||
uint16_t ch2_sbu2_mV;
|
||||
uint16_t ch3_sbu1_mV;
|
||||
uint16_t ch3_sbu2_mV;
|
||||
uint16_t ch4_sbu1_mV;
|
||||
uint16_t ch4_sbu2_mV;
|
||||
uint16_t ch5_sbu1_mV;
|
||||
uint16_t ch5_sbu2_mV;
|
||||
uint16_t ch6_sbu1_mV;
|
||||
uint16_t ch6_sbu2_mV;
|
||||
};
|
||||
@@ -1,261 +0,0 @@
|
||||
extern int _app_start[0xc000]; // Only first 3 sectors of size 0x4000 are used
|
||||
|
||||
int get_jungle_health_pkt(void *dat) {
|
||||
COMPILE_TIME_ASSERT(sizeof(struct jungle_health_t) <= USBPACKET_MAX_SIZE);
|
||||
struct jungle_health_t * health = (struct jungle_health_t*)dat;
|
||||
|
||||
health->uptime_pkt = uptime_cnt;
|
||||
health->ch1_power = current_board->get_channel_power(1U);
|
||||
health->ch2_power = current_board->get_channel_power(2U);
|
||||
health->ch3_power = current_board->get_channel_power(3U);
|
||||
health->ch4_power = current_board->get_channel_power(4U);
|
||||
health->ch5_power = current_board->get_channel_power(5U);
|
||||
health->ch6_power = current_board->get_channel_power(6U);
|
||||
|
||||
health->ch1_sbu1_mV = current_board->get_sbu_mV(1U, SBU1);
|
||||
health->ch1_sbu2_mV = current_board->get_sbu_mV(1U, SBU2);
|
||||
health->ch2_sbu1_mV = current_board->get_sbu_mV(2U, SBU1);
|
||||
health->ch2_sbu2_mV = current_board->get_sbu_mV(2U, SBU2);
|
||||
health->ch3_sbu1_mV = current_board->get_sbu_mV(3U, SBU1);
|
||||
health->ch3_sbu2_mV = current_board->get_sbu_mV(3U, SBU2);
|
||||
health->ch4_sbu1_mV = current_board->get_sbu_mV(4U, SBU1);
|
||||
health->ch4_sbu2_mV = current_board->get_sbu_mV(4U, SBU2);
|
||||
health->ch5_sbu1_mV = current_board->get_sbu_mV(5U, SBU1);
|
||||
health->ch5_sbu2_mV = current_board->get_sbu_mV(5U, SBU2);
|
||||
health->ch6_sbu1_mV = current_board->get_sbu_mV(6U, SBU1);
|
||||
health->ch6_sbu2_mV = current_board->get_sbu_mV(6U, SBU2);
|
||||
|
||||
return sizeof(*health);
|
||||
}
|
||||
|
||||
// send on serial, first byte to select the ring
|
||||
void comms_endpoint2_write(const uint8_t *data, uint32_t len) {
|
||||
UNUSED(data);
|
||||
UNUSED(len);
|
||||
}
|
||||
|
||||
int comms_control_handler(ControlPacket_t *req, uint8_t *resp) {
|
||||
unsigned int resp_len = 0;
|
||||
uint32_t time;
|
||||
|
||||
#ifdef DEBUG_COMMS
|
||||
print("raw control request: "); hexdump(req, sizeof(ControlPacket_t)); print("\n");
|
||||
print("- request "); puth(req->request); print("\n");
|
||||
print("- param1 "); puth(req->param1); print("\n");
|
||||
print("- param2 "); puth(req->param2); print("\n");
|
||||
#endif
|
||||
|
||||
switch (req->request) {
|
||||
// **** 0xa0: Set panda power.
|
||||
case 0xa0:
|
||||
current_board->set_panda_power((req->param1 == 1U));
|
||||
break;
|
||||
// **** 0xa1: Set harness orientation.
|
||||
case 0xa1:
|
||||
current_board->set_harness_orientation(req->param1);
|
||||
break;
|
||||
// **** 0xa2: Set ignition.
|
||||
case 0xa2:
|
||||
current_board->set_ignition((req->param1 == 1U));
|
||||
break;
|
||||
// **** 0xa0: Set panda power per channel by bitmask.
|
||||
case 0xa3:
|
||||
current_board->set_panda_individual_power(req->param1, (req->param2 > 0U));
|
||||
break;
|
||||
// **** 0xa8: get microsecond timer
|
||||
case 0xa8:
|
||||
time = microsecond_timer_get();
|
||||
resp[0] = (time & 0x000000FFU);
|
||||
resp[1] = ((time & 0x0000FF00U) >> 8U);
|
||||
resp[2] = ((time & 0x00FF0000U) >> 16U);
|
||||
resp[3] = ((time & 0xFF000000U) >> 24U);
|
||||
resp_len = 4U;
|
||||
break;
|
||||
// **** 0xc0: reset communications
|
||||
case 0xc0:
|
||||
comms_can_reset();
|
||||
break;
|
||||
// **** 0xc1: get hardware type
|
||||
case 0xc1:
|
||||
resp[0] = hw_type;
|
||||
resp_len = 1;
|
||||
break;
|
||||
// **** 0xc2: CAN health stats
|
||||
case 0xc2:
|
||||
COMPILE_TIME_ASSERT(sizeof(can_health_t) <= USBPACKET_MAX_SIZE);
|
||||
if (req->param1 < 3U) {
|
||||
update_can_health_pkt(req->param1, 0U);
|
||||
can_health[req->param1].can_speed = (bus_config[req->param1].can_speed / 10U);
|
||||
can_health[req->param1].can_data_speed = (bus_config[req->param1].can_data_speed / 10U);
|
||||
can_health[req->param1].canfd_enabled = bus_config[req->param1].canfd_enabled;
|
||||
can_health[req->param1].brs_enabled = bus_config[req->param1].brs_enabled;
|
||||
can_health[req->param1].canfd_non_iso = bus_config[req->param1].canfd_non_iso;
|
||||
resp_len = sizeof(can_health[req->param1]);
|
||||
(void)memcpy(resp, &can_health[req->param1], resp_len);
|
||||
}
|
||||
break;
|
||||
// **** 0xc3: fetch MCU UID
|
||||
case 0xc3:
|
||||
(void)memcpy(resp, ((uint8_t *)UID_BASE), 12);
|
||||
resp_len = 12;
|
||||
break;
|
||||
// **** 0xd0: fetch serial (aka the provisioned dongle ID)
|
||||
case 0xd0:
|
||||
// addresses are OTP
|
||||
if (req->param1 == 1U) {
|
||||
(void)memcpy(resp, (uint8_t *)DEVICE_SERIAL_NUMBER_ADDRESS, 0x10);
|
||||
resp_len = 0x10;
|
||||
} else {
|
||||
get_provision_chunk(resp);
|
||||
resp_len = PROVISION_CHUNK_LEN;
|
||||
}
|
||||
break;
|
||||
// **** 0xd1: enter bootloader mode
|
||||
case 0xd1:
|
||||
// this allows reflashing of the bootstub
|
||||
switch (req->param1) {
|
||||
case 0:
|
||||
// only allow bootloader entry on debug builds
|
||||
#ifdef ALLOW_DEBUG
|
||||
print("-> entering bootloader\n");
|
||||
enter_bootloader_mode = ENTER_BOOTLOADER_MAGIC;
|
||||
NVIC_SystemReset();
|
||||
#endif
|
||||
break;
|
||||
case 1:
|
||||
print("-> entering softloader\n");
|
||||
enter_bootloader_mode = ENTER_SOFTLOADER_MAGIC;
|
||||
NVIC_SystemReset();
|
||||
break;
|
||||
default:
|
||||
print("Bootloader mode invalid\n");
|
||||
break;
|
||||
}
|
||||
break;
|
||||
// **** 0xd2: get health packet
|
||||
case 0xd2:
|
||||
resp_len = get_jungle_health_pkt(resp);
|
||||
break;
|
||||
// **** 0xd3: get first 64 bytes of signature
|
||||
case 0xd3:
|
||||
{
|
||||
resp_len = 64;
|
||||
char * code = (char*)_app_start;
|
||||
int code_len = _app_start[0];
|
||||
(void)memcpy(resp, &code[code_len], resp_len);
|
||||
}
|
||||
break;
|
||||
// **** 0xd4: get second 64 bytes of signature
|
||||
case 0xd4:
|
||||
{
|
||||
resp_len = 64;
|
||||
char * code = (char*)_app_start;
|
||||
int code_len = _app_start[0];
|
||||
(void)memcpy(resp, &code[code_len + 64], resp_len);
|
||||
}
|
||||
break;
|
||||
// **** 0xd6: get version
|
||||
case 0xd6:
|
||||
COMPILE_TIME_ASSERT(sizeof(gitversion) <= USBPACKET_MAX_SIZE);
|
||||
(void)memcpy(resp, gitversion, sizeof(gitversion));
|
||||
resp_len = sizeof(gitversion) - 1U;
|
||||
break;
|
||||
// **** 0xd8: reset ST
|
||||
case 0xd8:
|
||||
NVIC_SystemReset();
|
||||
break;
|
||||
// **** 0xdb: set OBD CAN multiplexing mode
|
||||
case 0xdb:
|
||||
if (req->param1 == 1U) {
|
||||
// Enable OBD CAN
|
||||
current_board->set_can_mode(CAN_MODE_OBD_CAN2);
|
||||
} else {
|
||||
// Disable OBD CAN
|
||||
current_board->set_can_mode(CAN_MODE_NORMAL);
|
||||
}
|
||||
break;
|
||||
// **** 0xdd: get healthpacket and CANPacket versions
|
||||
case 0xdd:
|
||||
resp[0] = JUNGLE_HEALTH_PACKET_VERSION;
|
||||
resp[1] = CAN_PACKET_VERSION;
|
||||
resp[2] = CAN_HEALTH_PACKET_VERSION;
|
||||
resp_len = 3;
|
||||
break;
|
||||
// **** 0xde: set can bitrate
|
||||
case 0xde:
|
||||
if ((req->param1 < PANDA_BUS_CNT) && is_speed_valid(req->param2, speeds, sizeof(speeds)/sizeof(speeds[0]))) {
|
||||
bus_config[req->param1].can_speed = req->param2;
|
||||
bool ret = can_init(CAN_NUM_FROM_BUS_NUM(req->param1));
|
||||
UNUSED(ret);
|
||||
}
|
||||
break;
|
||||
// **** 0xe0: debug read
|
||||
case 0xe0:
|
||||
// read
|
||||
while ((resp_len < MIN(req->length, USBPACKET_MAX_SIZE)) && get_char(get_ring_by_number(0), (char*)&resp[resp_len])) {
|
||||
++resp_len;
|
||||
}
|
||||
break;
|
||||
// **** 0xe5: set CAN loopback (for testing)
|
||||
case 0xe5:
|
||||
can_loopback = (req->param1 > 0U);
|
||||
can_init_all();
|
||||
break;
|
||||
// **** 0xf1: Clear CAN ring buffer.
|
||||
case 0xf1:
|
||||
if (req->param1 == 0xFFFFU) {
|
||||
print("Clearing CAN Rx queue\n");
|
||||
can_clear(&can_rx_q);
|
||||
} else if (req->param1 < PANDA_BUS_CNT) {
|
||||
print("Clearing CAN Tx queue\n");
|
||||
can_clear(can_queues[req->param1]);
|
||||
} else {
|
||||
print("Clearing CAN CAN ring buffer failed: wrong bus number\n");
|
||||
}
|
||||
break;
|
||||
// **** 0xf2: Clear debug ring buffer.
|
||||
case 0xf2:
|
||||
print("Clearing debug queue.\n");
|
||||
clear_uart_buff(get_ring_by_number(0));
|
||||
break;
|
||||
// **** 0xf4: Set CAN transceiver enable pin
|
||||
case 0xf4:
|
||||
current_board->enable_can_transciever(req->param1, req->param2 > 0U);
|
||||
break;
|
||||
// **** 0xf5: Set CAN silent mode
|
||||
case 0xf5:
|
||||
can_silent = (req->param1 > 0U) ? ALL_CAN_SILENT : ALL_CAN_LIVE;
|
||||
can_init_all();
|
||||
break;
|
||||
// **** 0xf7: enable/disable header pin by number
|
||||
case 0xf7:
|
||||
current_board->enable_header_pin(req->param1, req->param2 > 0U);
|
||||
break;
|
||||
// **** 0xf9: set CAN FD data bitrate
|
||||
case 0xf9:
|
||||
if ((req->param1 < PANDA_CAN_CNT) &&
|
||||
current_board->has_canfd &&
|
||||
is_speed_valid(req->param2, data_speeds, sizeof(data_speeds)/sizeof(data_speeds[0]))) {
|
||||
bus_config[req->param1].can_data_speed = req->param2;
|
||||
bus_config[req->param1].canfd_enabled = (req->param2 >= bus_config[req->param1].can_speed);
|
||||
bus_config[req->param1].brs_enabled = (req->param2 > bus_config[req->param1].can_speed);
|
||||
bool ret = can_init(CAN_NUM_FROM_BUS_NUM(req->param1));
|
||||
UNUSED(ret);
|
||||
}
|
||||
break;
|
||||
// **** 0xfc: set CAN FD non-ISO mode
|
||||
case 0xfc:
|
||||
if ((req->param1 < PANDA_CAN_CNT) && current_board->has_canfd) {
|
||||
bus_config[req->param1].canfd_non_iso = (req->param2 != 0U);
|
||||
bool ret = can_init(CAN_NUM_FROM_BUS_NUM(req->param1));
|
||||
UNUSED(ret);
|
||||
}
|
||||
break;
|
||||
default:
|
||||
print("NO HANDLER ");
|
||||
puth(req->request);
|
||||
print("\n");
|
||||
break;
|
||||
}
|
||||
return resp_len;
|
||||
}
|
||||
BIN
panda/board/jungle/obj/bootstub.panda_jungle.bin
Executable file
BIN
panda/board/jungle/obj/bootstub.panda_jungle.bin
Executable file
Binary file not shown.
BIN
panda/board/jungle/obj/bootstub.panda_jungle.elf
Executable file
BIN
panda/board/jungle/obj/bootstub.panda_jungle.elf
Executable file
Binary file not shown.
BIN
panda/board/jungle/obj/bootstub.panda_jungle_h7.bin
Executable file
BIN
panda/board/jungle/obj/bootstub.panda_jungle_h7.bin
Executable file
Binary file not shown.
BIN
panda/board/jungle/obj/bootstub.panda_jungle_h7.elf
Executable file
BIN
panda/board/jungle/obj/bootstub.panda_jungle_h7.elf
Executable file
Binary file not shown.
BIN
panda/board/jungle/obj/panda_jungle.bin
Executable file
BIN
panda/board/jungle/obj/panda_jungle.bin
Executable file
Binary file not shown.
BIN
panda/board/jungle/obj/panda_jungle.bin.signed
Normal file
BIN
panda/board/jungle/obj/panda_jungle.bin.signed
Normal file
Binary file not shown.
BIN
panda/board/jungle/obj/panda_jungle.elf
Executable file
BIN
panda/board/jungle/obj/panda_jungle.elf
Executable file
Binary file not shown.
BIN
panda/board/jungle/obj/panda_jungle_h7.bin
Executable file
BIN
panda/board/jungle/obj/panda_jungle_h7.bin
Executable file
Binary file not shown.
BIN
panda/board/jungle/obj/panda_jungle_h7.bin.signed
Normal file
BIN
panda/board/jungle/obj/panda_jungle_h7.bin.signed
Normal file
Binary file not shown.
BIN
panda/board/jungle/obj/panda_jungle_h7.elf
Executable file
BIN
panda/board/jungle/obj/panda_jungle_h7.elf
Executable file
Binary file not shown.
@@ -1,7 +0,0 @@
|
||||
#include "boards/board_declarations.h"
|
||||
#include "boards/board_v1.h"
|
||||
|
||||
void detect_board_type(void) {
|
||||
hw_type = HW_TYPE_V1;
|
||||
current_board = &board_v1;
|
||||
}
|
||||
@@ -1,9 +0,0 @@
|
||||
#include "boards/board_declarations.h"
|
||||
|
||||
#include "stm32h7/lladc.h"
|
||||
#include "boards/board_v2.h"
|
||||
|
||||
void detect_board_type(void) {
|
||||
hw_type = HW_TYPE_V2;
|
||||
current_board = &board_v2;
|
||||
}
|
||||
@@ -1,54 +0,0 @@
|
||||
|
||||
typedef struct {
|
||||
ADC_TypeDef *adc;
|
||||
uint8_t channel;
|
||||
} adc_channel_t;
|
||||
|
||||
void adc_init(ADC_TypeDef *adc) {
|
||||
adc->CR &= ~(ADC_CR_DEEPPWD); // Reset deep-power-down mode
|
||||
adc->CR |= ADC_CR_ADVREGEN; // Enable ADC regulator
|
||||
while(!(adc->ISR & ADC_ISR_LDORDY) && (adc != ADC3));
|
||||
|
||||
if (adc != ADC3) {
|
||||
adc->CR &= ~(ADC_CR_ADCALDIF); // Choose single-ended calibration
|
||||
adc->CR |= ADC_CR_ADCALLIN; // Lineriality calibration
|
||||
}
|
||||
adc->CR |= ADC_CR_ADCAL; // Start calibrtation
|
||||
while((adc->CR & ADC_CR_ADCAL) != 0);
|
||||
|
||||
adc->ISR |= ADC_ISR_ADRDY;
|
||||
adc->CR |= ADC_CR_ADEN;
|
||||
while(!(adc->ISR & ADC_ISR_ADRDY));
|
||||
}
|
||||
|
||||
uint16_t adc_get_raw(ADC_TypeDef *adc, uint8_t channel) {
|
||||
adc->SQR1 &= ~(ADC_SQR1_L);
|
||||
adc->SQR1 = ((uint32_t) channel << 6U);
|
||||
|
||||
if (channel < 10U) {
|
||||
adc->SMPR1 = (0x7U << (channel * 3U));
|
||||
} else {
|
||||
adc->SMPR2 = (0x7U << ((channel - 10U) * 3U));
|
||||
}
|
||||
adc->PCSEL_RES0 = (0x1U << channel);
|
||||
|
||||
adc->CR |= ADC_CR_ADSTART;
|
||||
while (!(adc->ISR & ADC_ISR_EOC));
|
||||
|
||||
uint16_t res = adc->DR;
|
||||
|
||||
while (!(adc->ISR & ADC_ISR_EOS));
|
||||
adc->ISR |= ADC_ISR_EOS;
|
||||
|
||||
return res;
|
||||
}
|
||||
|
||||
uint16_t adc_get_mV(ADC_TypeDef *adc, uint8_t channel) {
|
||||
uint16_t ret = 0;
|
||||
if ((adc == ADC1) || (adc == ADC2)) {
|
||||
ret = (adc_get_raw(adc, channel) * current_board->avdd_mV) / 65535U;
|
||||
} else if (adc == ADC3) {
|
||||
ret = (adc_get_raw(adc, channel) * current_board->avdd_mV) / 4095U;
|
||||
} else {}
|
||||
return ret;
|
||||
}
|
||||
Reference in New Issue
Block a user