mirror of
https://github.com/infiniteCable2/panda.git
synced 2026-02-18 09:13:52 +08:00
Merge branch 'master' of https://github.com/sunnypilot/panda
This commit is contained in:
7
Jenkinsfile
vendored
7
Jenkinsfile
vendored
@@ -32,6 +32,7 @@ export GIT_BRANCH=${env.GIT_BRANCH}
|
||||
export GIT_COMMIT=${env.GIT_COMMIT}
|
||||
export PYTHONPATH=${env.TEST_DIR}/../
|
||||
export PYTHONWARNINGS=error
|
||||
export LOGLEVEL=debug
|
||||
ln -sf /data/openpilot/opendbc_repo/opendbc /data/opendbc
|
||||
|
||||
# TODO: this is an agnos issue
|
||||
@@ -85,7 +86,7 @@ pipeline {
|
||||
steps {
|
||||
timeout(time: 20, unit: 'MINUTES') {
|
||||
script {
|
||||
dockerImage = docker.build("${env.DOCKER_IMAGE_TAG}", "--build-arg CACHEBUST=${env.BUILD_NUMBER} .")
|
||||
dockerImage = docker.build("${env.DOCKER_IMAGE_TAG}", "--build-arg CACHEBUST=${env.GIT_COMMIT} .")
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -109,7 +110,7 @@ pipeline {
|
||||
["build", "scons -j4"],
|
||||
["flash", "cd scripts/ && ./reflash_internal_panda.py"],
|
||||
["flash jungle", "cd board/jungle && ./flash.py --all"],
|
||||
["test", "cd tests/hitl && HW_TYPES=10 pytest --durations=0 2*.py [5-9]*.py"],
|
||||
["test", "cd tests/hitl && pytest --durations=0 2*.py [5-9]*.py"],
|
||||
])
|
||||
}
|
||||
}
|
||||
@@ -121,7 +122,7 @@ pipeline {
|
||||
["build", "scons -j4"],
|
||||
["flash", "cd scripts/ && ./reflash_internal_panda.py"],
|
||||
["flash jungle", "cd board/jungle && ./flash.py --all"],
|
||||
["test", "cd tests/hitl && HW_TYPES=9 pytest --durations=0 2*.py [5-9]*.py"],
|
||||
["test", "cd tests/hitl && pytest --durations=0 2*.py [5-9]*.py"],
|
||||
])
|
||||
}
|
||||
}
|
||||
|
||||
@@ -29,10 +29,9 @@ struct board {
|
||||
const uint8_t led_pin[3];
|
||||
const uint8_t led_pwm_channels[3]; // leave at 0 to disable PWM
|
||||
const bool has_spi;
|
||||
const uint16_t fan_max_rpm;
|
||||
const bool has_fan;
|
||||
const uint16_t avdd_mV;
|
||||
const uint8_t fan_enable_cooldown_time;
|
||||
const uint8_t fan_max_pwm;
|
||||
board_init init;
|
||||
board_init_bootloader init_bootloader;
|
||||
board_enable_can_transceiver enable_can_transceiver;
|
||||
|
||||
@@ -42,24 +42,6 @@ static void cuatro_set_bootkick(BootState state) {
|
||||
}
|
||||
|
||||
static void cuatro_set_amp_enabled(bool enabled) {
|
||||
// *** tmp, remove soon ***
|
||||
static const uint8_t olds[][12] = {
|
||||
{0x44, 0x00, 0x10, 0x00, 0x19, 0x51, 0x32, 0x34, 0x39, 0x37, 0x37, 0x30},
|
||||
{0x14, 0x00, 0x13, 0x00, 0x18, 0x51, 0x32, 0x34, 0x39, 0x37, 0x37, 0x30},
|
||||
{0x04, 0x00, 0x30, 0x00, 0x18, 0x51, 0x32, 0x34, 0x39, 0x37, 0x37, 0x30},
|
||||
{0x2f, 0x00, 0x14, 0x00, 0x18, 0x51, 0x32, 0x34, 0x39, 0x37, 0x37, 0x30},
|
||||
{0x1e, 0x00, 0x2f, 0x00, 0x18, 0x51, 0x32, 0x34, 0x39, 0x37, 0x37, 0x30},
|
||||
{0x26, 0x00, 0x15, 0x00, 0x19, 0x51, 0x32, 0x34, 0x39, 0x37, 0x37, 0x30},
|
||||
{0x35, 0x00, 0x32, 0x00, 0x18, 0x51, 0x32, 0x34, 0x39, 0x37, 0x37, 0x30},
|
||||
{0x37, 0x00, 0x2f, 0x00, 0x18, 0x51, 0x32, 0x34, 0x39, 0x37, 0x37, 0x30},
|
||||
};
|
||||
bool is_old = false;
|
||||
for (uint8_t i = 0U; i < (sizeof(olds) / sizeof(olds[0])); i++) {
|
||||
is_old |= (memcmp(olds[i], ((uint8_t *)UID_BASE), 12) == 0);
|
||||
}
|
||||
if (is_old) set_gpio_output(GPIOA, 5, enabled);
|
||||
// *** tmp end ***
|
||||
|
||||
set_gpio_output(GPIOB, 0, enabled);
|
||||
}
|
||||
|
||||
@@ -132,8 +114,7 @@ static harness_configuration cuatro_harness_config = {
|
||||
board board_cuatro = {
|
||||
.harness_config = &cuatro_harness_config,
|
||||
.has_spi = true,
|
||||
.fan_max_rpm = 12500U,
|
||||
.fan_max_pwm = 99U, // it can go up to 14k RPM, but 99% -> 100% is very non-linear
|
||||
.has_fan = true,
|
||||
.avdd_mV = 1800U,
|
||||
.fan_enable_cooldown_time = 3U,
|
||||
.init = cuatro_init,
|
||||
|
||||
@@ -115,8 +115,7 @@ board board_red = {
|
||||
.set_bootkick = unused_set_bootkick,
|
||||
.harness_config = &red_harness_config,
|
||||
.has_spi = false,
|
||||
.fan_max_rpm = 0U,
|
||||
.fan_max_pwm = 100U,
|
||||
.has_fan = false,
|
||||
.avdd_mV = 3300U,
|
||||
.fan_enable_cooldown_time = 0U,
|
||||
.init = red_init,
|
||||
|
||||
@@ -155,8 +155,7 @@ static harness_configuration tres_harness_config = {
|
||||
board board_tres = {
|
||||
.harness_config = &tres_harness_config,
|
||||
.has_spi = true,
|
||||
.fan_max_rpm = 6600U,
|
||||
.fan_max_pwm = 100U,
|
||||
.has_fan = true,
|
||||
.avdd_mV = 1800U,
|
||||
.fan_enable_cooldown_time = 3U,
|
||||
.init = tres_init,
|
||||
|
||||
@@ -1,7 +1,5 @@
|
||||
#pragma once
|
||||
#include "can_declarations.h"
|
||||
|
||||
static const uint8_t PANDA_CAN_CNT = 3U;
|
||||
static const uint8_t PANDA_BUS_CNT = 3U;
|
||||
#define PANDA_CAN_CNT 3U
|
||||
|
||||
static const unsigned char dlc_to_len[] = {0U, 1U, 2U, 3U, 4U, 5U, 6U, 7U, 8U, 12U, 16U, 20U, 24U, 32U, 48U, 64U};
|
||||
#include "opendbc/safety/can.h"
|
||||
|
||||
@@ -1,24 +0,0 @@
|
||||
#pragma once
|
||||
|
||||
// bump this when changing the CAN packet
|
||||
#define CAN_PACKET_VERSION 4
|
||||
|
||||
#define CANPACKET_HEAD_SIZE 6U
|
||||
|
||||
#define CANPACKET_DATA_SIZE_MAX 64U
|
||||
|
||||
typedef struct {
|
||||
unsigned char fd : 1;
|
||||
unsigned char bus : 3;
|
||||
unsigned char data_len_code : 4; // lookup length with dlc_to_len
|
||||
unsigned char rejected : 1;
|
||||
unsigned char returned : 1;
|
||||
unsigned char extended : 1;
|
||||
unsigned int addr : 29;
|
||||
unsigned char checksum;
|
||||
unsigned char data[CANPACKET_DATA_SIZE_MAX];
|
||||
} __attribute__((packed, aligned(4))) CANPacket_t;
|
||||
|
||||
#define GET_BUS(msg) ((msg)->bus)
|
||||
#define GET_LEN(msg) (dlc_to_len[(msg)->data_len_code])
|
||||
#define GET_ADDR(msg) ((msg)->addr)
|
||||
@@ -5,15 +5,13 @@ uint32_t safety_rx_invalid = 0;
|
||||
uint32_t tx_buffer_overflow = 0;
|
||||
uint32_t rx_buffer_overflow = 0;
|
||||
|
||||
can_health_t can_health[CAN_HEALTH_ARRAY_SIZE] = {{0}, {0}, {0}};
|
||||
can_health_t can_health[PANDA_CAN_CNT] = {{0}, {0}, {0}};
|
||||
|
||||
// Ignition detected from CAN meessages
|
||||
bool ignition_can = false;
|
||||
uint32_t ignition_can_cnt = 0U;
|
||||
|
||||
int can_live = 0;
|
||||
int pending_can_live = 0;
|
||||
int can_silent = ALL_CAN_SILENT;
|
||||
bool can_silent = true;
|
||||
bool can_loopback = false;
|
||||
|
||||
// ********************* instantiate queues *********************
|
||||
@@ -39,7 +37,7 @@ can_buffer(tx3_q, CAN_TX_BUFFER_SIZE)
|
||||
|
||||
// FIXME:
|
||||
// cppcheck-suppress misra-c2012-9.3
|
||||
can_ring *can_queues[CAN_QUEUES_ARRAY_SIZE] = {&can_tx1_q, &can_tx2_q, &can_tx3_q};
|
||||
can_ring *can_queues[PANDA_CAN_CNT] = {&can_tx1_q, &can_tx2_q, &can_tx3_q};
|
||||
|
||||
// ********************* interrupt safe queue *********************
|
||||
bool can_pop(can_ring *q, CANPacket_t *elem) {
|
||||
@@ -130,15 +128,15 @@ void can_clear(can_ring *q) {
|
||||
|
||||
// Helpers
|
||||
// Panda: Bus 0=CAN1 Bus 1=CAN2 Bus 2=CAN3
|
||||
bus_config_t bus_config[BUS_CONFIG_ARRAY_SIZE] = {
|
||||
bus_config_t bus_config[PANDA_CAN_CNT] = {
|
||||
{ .bus_lookup = 0U, .can_num_lookup = 0U, .forwarding_bus = -1, .can_speed = 5000U, .can_data_speed = 20000U, .canfd_auto = false, .canfd_enabled = false, .brs_enabled = false, .canfd_non_iso = false },
|
||||
{ .bus_lookup = 1U, .can_num_lookup = 1U, .forwarding_bus = -1, .can_speed = 5000U, .can_data_speed = 20000U, .canfd_auto = false, .canfd_enabled = false, .brs_enabled = false, .canfd_non_iso = false },
|
||||
{ .bus_lookup = 2U, .can_num_lookup = 2U, .forwarding_bus = -1, .can_speed = 5000U, .can_data_speed = 20000U, .canfd_auto = false, .canfd_enabled = false, .brs_enabled = false, .canfd_non_iso = false },
|
||||
{ .bus_lookup = 0xFFU, .can_num_lookup = 0xFFU, .forwarding_bus = -1, .can_speed = 333U, .can_data_speed = 333U, .canfd_auto = false, .canfd_enabled = false, .brs_enabled = false, .canfd_non_iso = false },
|
||||
};
|
||||
|
||||
void can_init_all(void) {
|
||||
for (uint8_t i=0U; i < PANDA_CAN_CNT; i++) {
|
||||
bus_config[i].canfd_enabled = false;
|
||||
can_clear(can_queues[i]);
|
||||
(void)can_init(i);
|
||||
}
|
||||
@@ -158,20 +156,18 @@ void can_set_forwarding(uint8_t from, uint8_t to) {
|
||||
#endif
|
||||
|
||||
void ignition_can_hook(CANPacket_t *msg) {
|
||||
int bus = GET_BUS(msg);
|
||||
if (bus == 0) {
|
||||
int addr = GET_ADDR(msg);
|
||||
if (msg->bus == 0U) {
|
||||
int len = GET_LEN(msg);
|
||||
|
||||
// GM exception
|
||||
if ((addr == 0x1F1) && (len == 8)) {
|
||||
if ((msg->addr == 0x1F1U) && (len == 8)) {
|
||||
// SystemPowerMode (2=Run, 3=Crank Request)
|
||||
ignition_can = (msg->data[0] & 0x2U) != 0U;
|
||||
ignition_can_cnt = 0U;
|
||||
}
|
||||
|
||||
// Rivian R1S/T GEN1 exception
|
||||
if ((addr == 0x152) && (len == 8)) {
|
||||
if ((msg->addr == 0x152U) && (len == 8)) {
|
||||
// 0x152 overlaps with Subaru pre-global which has this bit as the high beam
|
||||
int counter = msg->data[1] & 0xFU; // max is only 14
|
||||
|
||||
@@ -185,7 +181,7 @@ void ignition_can_hook(CANPacket_t *msg) {
|
||||
}
|
||||
|
||||
// Tesla Model 3/Y exception
|
||||
if ((addr == 0x221) && (len == 8)) {
|
||||
if ((msg->addr == 0x221U) && (len == 8)) {
|
||||
// 0x221 overlaps with Rivian which has random data on byte 0
|
||||
int counter = msg->data[6] >> 4;
|
||||
|
||||
@@ -200,7 +196,7 @@ void ignition_can_hook(CANPacket_t *msg) {
|
||||
}
|
||||
|
||||
// Mazda exception
|
||||
if ((addr == 0x9E) && (len == 8)) {
|
||||
if ((msg->addr == 0x9EU) && (len == 8)) {
|
||||
ignition_can = (msg->data[0] >> 5) == 0x6U;
|
||||
ignition_can_cnt = 0U;
|
||||
}
|
||||
@@ -240,7 +236,7 @@ bool can_check_checksum(CANPacket_t *packet) {
|
||||
|
||||
void can_send(CANPacket_t *to_push, uint8_t bus_number, bool skip_tx_hook) {
|
||||
if (skip_tx_hook || safety_tx_hook(to_push) != 0) {
|
||||
if (bus_number < PANDA_BUS_CNT) {
|
||||
if (bus_number < PANDA_CAN_CNT) {
|
||||
// add CAN packet to send queue
|
||||
tx_buffer_overflow += can_push(can_queues[bus_number], to_push) ? 0U : 1U;
|
||||
process_can(CAN_NUM_FROM_BUS_NUM(bus_number));
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
#pragma once
|
||||
|
||||
#include "board/can.h"
|
||||
|
||||
typedef struct {
|
||||
volatile uint32_t w_ptr;
|
||||
volatile uint32_t r_ptr;
|
||||
@@ -24,19 +26,13 @@ extern uint32_t safety_rx_invalid;
|
||||
extern uint32_t tx_buffer_overflow;
|
||||
extern uint32_t rx_buffer_overflow;
|
||||
|
||||
#define CAN_HEALTH_ARRAY_SIZE 3
|
||||
extern can_health_t can_health[CAN_HEALTH_ARRAY_SIZE];
|
||||
extern can_health_t can_health[PANDA_CAN_CNT];
|
||||
|
||||
// Ignition detected from CAN meessages
|
||||
extern bool ignition_can;
|
||||
extern uint32_t ignition_can_cnt;
|
||||
|
||||
#define ALL_CAN_SILENT 0xFF
|
||||
#define ALL_CAN_LIVE 0
|
||||
|
||||
extern int can_live;
|
||||
extern int pending_can_live;
|
||||
extern int can_silent;
|
||||
extern bool can_silent;
|
||||
extern bool can_loopback;
|
||||
|
||||
// ******************* functions prototypes *********************
|
||||
@@ -44,8 +40,7 @@ bool can_init(uint8_t can_number);
|
||||
void process_can(uint8_t can_number);
|
||||
|
||||
// ********************* instantiate queues *********************
|
||||
#define CAN_QUEUES_ARRAY_SIZE 3
|
||||
extern can_ring *can_queues[CAN_QUEUES_ARRAY_SIZE];
|
||||
extern can_ring *can_queues[PANDA_CAN_CNT];
|
||||
|
||||
// helpers
|
||||
#define WORD_TO_BYTE_ARRAY(dst8, src32) 0[dst8] = ((src32) & 0xFFU); 1[dst8] = (((src32) >> 8U) & 0xFFU); 2[dst8] = (((src32) >> 16U) & 0xFFU); 3[dst8] = (((src32) >> 24U) & 0xFFU)
|
||||
@@ -55,20 +50,7 @@ extern can_ring *can_queues[CAN_QUEUES_ARRAY_SIZE];
|
||||
bool can_pop(can_ring *q, CANPacket_t *elem);
|
||||
bool can_push(can_ring *q, const CANPacket_t *elem);
|
||||
uint32_t can_slots_empty(const can_ring *q);
|
||||
|
||||
// assign CAN numbering
|
||||
// bus num: CAN Bus numbers in panda, sent to/from USB
|
||||
// Min: 0; Max: 127; Bit 7 marks message as receipt (bus 129 is receipt for but 1)
|
||||
// cans: Look up MCU can interface from bus number
|
||||
// can number: numeric lookup for MCU CAN interfaces (0 = CAN1, 1 = CAN2, etc);
|
||||
// bus_lookup: Translates from 'can number' to 'bus number'.
|
||||
// can_num_lookup: Translates from 'bus number' to 'can number'.
|
||||
// forwarding bus: If >= 0, forward all messages from this bus to the specified bus.
|
||||
|
||||
// Helpers
|
||||
// Panda: Bus 0=CAN1 Bus 1=CAN2 Bus 2=CAN3
|
||||
#define BUS_CONFIG_ARRAY_SIZE 4
|
||||
extern bus_config_t bus_config[BUS_CONFIG_ARRAY_SIZE];
|
||||
extern bus_config_t bus_config[PANDA_CAN_CNT];
|
||||
|
||||
#define CANIF_FROM_CAN_NUM(num) (cans[num])
|
||||
#define BUS_NUM_FROM_CAN_NUM(num) (bus_config[num].bus_lookup)
|
||||
|
||||
@@ -5,10 +5,13 @@ struct fan_state_t fan_state;
|
||||
static const uint8_t FAN_TICK_FREQ = 8U;
|
||||
|
||||
void fan_set_power(uint8_t percentage) {
|
||||
fan_state.target_rpm = ((current_board->fan_max_rpm * CLAMP(percentage, 0U, 100U)) / 100U);
|
||||
if (percentage > 0U) {
|
||||
fan_state.power = CLAMP(percentage, 20U, 100U);
|
||||
} else {
|
||||
fan_state.power = 0U;
|
||||
}
|
||||
}
|
||||
|
||||
void llfan_init(void);
|
||||
void fan_init(void) {
|
||||
fan_state.cooldown_counter = current_board->fan_enable_cooldown_time * FAN_TICK_FREQ;
|
||||
llfan_init();
|
||||
@@ -16,9 +19,7 @@ void fan_init(void) {
|
||||
|
||||
// Call this at FAN_TICK_FREQ
|
||||
void fan_tick(void) {
|
||||
const float FAN_I = 6.5f;
|
||||
|
||||
if (current_board->fan_max_rpm > 0U) {
|
||||
if (current_board->has_fan) {
|
||||
// Measure fan RPM
|
||||
uint16_t fan_rpm_fast = fan_state.tach_counter * (60U * FAN_TICK_FREQ / 4U); // 4 interrupts per rotation
|
||||
fan_state.tach_counter = 0U;
|
||||
@@ -31,8 +32,8 @@ void fan_tick(void) {
|
||||
print("\n");
|
||||
#endif
|
||||
|
||||
// Cooldown counter
|
||||
if (fan_state.target_rpm > 0U) {
|
||||
// Cooldown counter to prevent noise on tachometer line.
|
||||
if (fan_state.power > 0U) {
|
||||
fan_state.cooldown_counter = current_board->fan_enable_cooldown_time * FAN_TICK_FREQ;
|
||||
} else {
|
||||
if (fan_state.cooldown_counter > 0U) {
|
||||
@@ -40,18 +41,8 @@ void fan_tick(void) {
|
||||
}
|
||||
}
|
||||
|
||||
// Update controller
|
||||
if (fan_state.target_rpm == 0U) {
|
||||
fan_state.error_integral = 0.0f;
|
||||
} else {
|
||||
float error = (fan_state.target_rpm - fan_rpm_fast) / ((float) current_board->fan_max_rpm);
|
||||
fan_state.error_integral += FAN_I * error;
|
||||
}
|
||||
fan_state.error_integral = CLAMP(fan_state.error_integral, 0U, current_board->fan_max_pwm);
|
||||
fan_state.power = fan_state.error_integral;
|
||||
|
||||
// Set PWM and enable line
|
||||
pwm_set(TIM3, 3, fan_state.power);
|
||||
current_board->set_fan_enabled((fan_state.target_rpm > 0U) || (fan_state.cooldown_counter > 0U));
|
||||
current_board->set_fan_enabled((fan_state.power > 0U) || (fan_state.cooldown_counter > 0U));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,7 +3,6 @@
|
||||
struct fan_state_t {
|
||||
uint16_t tach_counter;
|
||||
uint16_t rpm;
|
||||
uint16_t target_rpm;
|
||||
uint8_t power;
|
||||
float error_integral;
|
||||
uint8_t cooldown_counter;
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
#include "fdcan_declarations.h"
|
||||
|
||||
FDCAN_GlobalTypeDef *cans[CANS_ARRAY_SIZE] = {FDCAN1, FDCAN2, FDCAN3};
|
||||
FDCAN_GlobalTypeDef *cans[PANDA_CAN_CNT] = {FDCAN1, FDCAN2, FDCAN3};
|
||||
|
||||
static bool can_set_speed(uint8_t can_number) {
|
||||
bool ret = true;
|
||||
@@ -13,7 +13,7 @@ static bool can_set_speed(uint8_t can_number) {
|
||||
bus_config[bus_number].can_data_speed,
|
||||
bus_config[bus_number].canfd_non_iso,
|
||||
can_loopback,
|
||||
(unsigned int)(can_silent) & (1U << can_number)
|
||||
can_silent
|
||||
);
|
||||
return ret;
|
||||
}
|
||||
@@ -32,7 +32,7 @@ void can_clear_send(FDCAN_GlobalTypeDef *FDCANx, uint8_t can_number) {
|
||||
}
|
||||
|
||||
void update_can_health_pkt(uint8_t can_number, uint32_t ir_reg) {
|
||||
uint8_t can_irq_number[3][2] = {
|
||||
uint8_t can_irq_number[PANDA_CAN_CNT][2] = {
|
||||
{ FDCAN1_IT0_IRQn, FDCAN1_IT1_IRQn },
|
||||
{ FDCAN2_IT0_IRQn, FDCAN2_IT1_IRQn },
|
||||
{ FDCAN3_IT0_IRQn, FDCAN3_IT1_IRQn },
|
||||
@@ -63,7 +63,6 @@ void update_can_health_pkt(uint8_t can_number, uint32_t ir_reg) {
|
||||
can_health[can_number].irq0_call_rate = interrupts[can_irq_number[can_number][0]].call_rate;
|
||||
can_health[can_number].irq1_call_rate = interrupts[can_irq_number[can_number][1]].call_rate;
|
||||
|
||||
|
||||
if (ir_reg != 0U) {
|
||||
// Clear error interrupts
|
||||
FDCANx->IR |= (FDCAN_IR_PED | FDCAN_IR_PEA | FDCAN_IR_EP | FDCAN_IR_BO | FDCAN_IR_RF0L);
|
||||
@@ -158,17 +157,13 @@ void can_rx(uint8_t can_number) {
|
||||
|
||||
// Clear all new messages from Rx FIFO 0
|
||||
FDCANx->IR |= FDCAN_IR_RF0N;
|
||||
while((FDCANx->RXF0S & FDCAN_RXF0S_F0FL) != 0U) {
|
||||
while ((FDCANx->RXF0S & FDCAN_RXF0S_F0FL) != 0U) {
|
||||
can_health[can_number].total_rx_cnt += 1U;
|
||||
|
||||
// can is live
|
||||
pending_can_live = 1;
|
||||
|
||||
// get the index of the next RX FIFO element (0 to FDCAN_RX_FIFO_0_EL_CNT - 1)
|
||||
uint32_t rx_fifo_idx = (uint8_t)((FDCANx->RXF0S >> FDCAN_RXF0S_F0GI_Pos) & 0x3FU);
|
||||
|
||||
// Recommended to offset get index by at least +1 if RX FIFO is in overwrite mode and full (datasheet)
|
||||
if((FDCANx->RXF0S & FDCAN_RXF0S_F0F) == FDCAN_RXF0S_F0F) {
|
||||
if ((FDCANx->RXF0S & FDCAN_RXF0S_F0F) == FDCAN_RXF0S_F0F) {
|
||||
rx_fifo_idx = ((rx_fifo_idx + 1U) >= FDCAN_RX_FIFO_0_EL_CNT) ? 0U : (rx_fifo_idx + 1U);
|
||||
can_health[can_number].total_rx_lost_cnt += 1U; // At least one message was lost
|
||||
}
|
||||
|
||||
@@ -1,26 +1,19 @@
|
||||
#pragma once
|
||||
|
||||
// IRQs: FDCAN1_IT0, FDCAN1_IT1
|
||||
// FDCAN2_IT0, FDCAN2_IT1
|
||||
// FDCAN3_IT0, FDCAN3_IT1
|
||||
#include "board/can.h"
|
||||
|
||||
typedef struct {
|
||||
volatile uint32_t header[2];
|
||||
volatile uint32_t data_word[CANPACKET_DATA_SIZE_MAX/4U];
|
||||
} canfd_fifo;
|
||||
|
||||
#define CANS_ARRAY_SIZE 3
|
||||
extern FDCAN_GlobalTypeDef *cans[CANS_ARRAY_SIZE];
|
||||
extern FDCAN_GlobalTypeDef *cans[PANDA_CAN_CNT];
|
||||
|
||||
#define CAN_ACK_ERROR 3U
|
||||
|
||||
void can_clear_send(FDCAN_GlobalTypeDef *FDCANx, uint8_t can_number);
|
||||
void update_can_health_pkt(uint8_t can_number, uint32_t ir_reg);
|
||||
|
||||
// ***************************** CAN *****************************
|
||||
// FDFDCANx_IT1 IRQ Handler (TX)
|
||||
void process_can(uint8_t can_number);
|
||||
// FDFDCANx_IT0 IRQ Handler (RX and errors)
|
||||
// blink blue when we are receiving CAN messages
|
||||
void can_rx(uint8_t can_number);
|
||||
bool can_init(uint8_t can_number);
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
#define LED_GREEN 1U
|
||||
#define LED_BLUE 2U
|
||||
|
||||
#define LED_PWM_POWER 5U
|
||||
#define LED_PWM_POWER 2U
|
||||
|
||||
void led_set(uint8_t color, bool enabled) {
|
||||
if (color < 3U) {
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
#define PWM_COUNTER_OVERFLOW 2000U // To get ~50kHz
|
||||
#define PWM_COUNTER_OVERFLOW 4800U // To get ~25kHz
|
||||
|
||||
// TODO: Implement for 32-bit timers
|
||||
|
||||
|
||||
@@ -3,10 +3,8 @@
|
||||
#include "board/drivers/spi_declarations.h"
|
||||
#include "board/crc.h"
|
||||
|
||||
#define SPI_BUF_SIZE 2048U
|
||||
// H7 DMA2 located in D2 domain, so we need to use SRAM1/SRAM2
|
||||
__attribute__((section(".sram12"))) uint8_t spi_buf_rx[SPI_BUF_SIZE];
|
||||
__attribute__((section(".sram12"))) uint8_t spi_buf_tx[SPI_BUF_SIZE];
|
||||
uint8_t spi_buf_rx[SPI_BUF_SIZE];
|
||||
uint8_t spi_buf_tx[SPI_BUF_SIZE];
|
||||
|
||||
uint16_t spi_error_count = 0;
|
||||
|
||||
@@ -202,7 +200,7 @@ void spi_rx_done(void) {
|
||||
llspi_miso_dma(spi_buf_tx, response_len);
|
||||
|
||||
spi_state = next_rx_state;
|
||||
if (!checksum_valid && (spi_error_count < UINT16_MAX)) {
|
||||
if (!checksum_valid) {
|
||||
spi_error_count += 1U;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -8,7 +8,7 @@
|
||||
// in a tight loop, plus some buffer
|
||||
#define SPI_IRQ_RATE 16000U
|
||||
|
||||
#define SPI_BUF_SIZE 2048U
|
||||
#define SPI_BUF_SIZE 4096U
|
||||
// H7 DMA2 located in D2 domain, so we need to use SRAM1/SRAM2
|
||||
__attribute__((section(".sram12"))) extern uint8_t spi_buf_rx[SPI_BUF_SIZE];
|
||||
__attribute__((section(".sram12"))) extern uint8_t spi_buf_tx[SPI_BUF_SIZE];
|
||||
|
||||
@@ -11,20 +11,10 @@
|
||||
#define FAULT_INTERRUPT_RATE_CAN_2 (1UL << 3)
|
||||
#define FAULT_INTERRUPT_RATE_CAN_3 (1UL << 4)
|
||||
#define FAULT_INTERRUPT_RATE_TACH (1UL << 5)
|
||||
#define FAULT_INTERRUPT_RATE_GMLAN (1UL << 6) // deprecated
|
||||
#define FAULT_INTERRUPT_RATE_INTERRUPTS (1UL << 7)
|
||||
#define FAULT_INTERRUPT_RATE_SPI_DMA (1UL << 8)
|
||||
#define FAULT_INTERRUPT_RATE_SPI_CS (1UL << 9)
|
||||
#define FAULT_INTERRUPT_RATE_UART_1 (1UL << 10)
|
||||
#define FAULT_INTERRUPT_RATE_UART_2 (1UL << 11)
|
||||
#define FAULT_INTERRUPT_RATE_UART_3 (1UL << 12)
|
||||
#define FAULT_INTERRUPT_RATE_UART_5 (1UL << 13)
|
||||
#define FAULT_INTERRUPT_RATE_UART_DMA (1UL << 14)
|
||||
#define FAULT_INTERRUPT_RATE_USB (1UL << 15)
|
||||
#define FAULT_INTERRUPT_RATE_TIM1 (1UL << 16)
|
||||
#define FAULT_INTERRUPT_RATE_TIM3 (1UL << 17)
|
||||
#define FAULT_REGISTER_DIVERGENT (1UL << 18)
|
||||
#define FAULT_INTERRUPT_RATE_KLINE_INIT (1UL << 19)
|
||||
#define FAULT_INTERRUPT_RATE_CLOCK_SOURCE (1UL << 20)
|
||||
#define FAULT_INTERRUPT_RATE_TICK (1UL << 21)
|
||||
#define FAULT_INTERRUPT_RATE_EXTI (1UL << 22)
|
||||
|
||||
@@ -48,7 +48,7 @@ class PandaJungle(Panda):
|
||||
|
||||
@classmethod
|
||||
def spi_connect(cls, serial, ignore_version=False):
|
||||
return None, None, None, None, None
|
||||
return None, None, None, None
|
||||
|
||||
def flash(self, fn=None, code=None, reconnect=True):
|
||||
if not fn:
|
||||
|
||||
@@ -175,7 +175,7 @@ int main(void) {
|
||||
print("**** INTERRUPTS ON ****\n");
|
||||
enable_interrupts();
|
||||
|
||||
can_silent = ALL_CAN_LIVE;
|
||||
can_silent = false;
|
||||
set_safety_hooks(SAFETY_ALLOUTPUT, 0U);
|
||||
|
||||
can_init_all();
|
||||
|
||||
@@ -189,7 +189,7 @@ int comms_control_handler(ControlPacket_t *req, uint8_t *resp) {
|
||||
break;
|
||||
// **** 0xde: set can bitrate
|
||||
case 0xde:
|
||||
if ((req->param1 < PANDA_BUS_CNT) && is_speed_valid(req->param2, speeds, sizeof(speeds)/sizeof(speeds[0]))) {
|
||||
if ((req->param1 < PANDA_CAN_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);
|
||||
@@ -212,7 +212,7 @@ int comms_control_handler(ControlPacket_t *req, uint8_t *resp) {
|
||||
if (req->param1 == 0xFFFFU) {
|
||||
print("Clearing CAN Rx queue\n");
|
||||
can_clear(&can_rx_q);
|
||||
} else if (req->param1 < PANDA_BUS_CNT) {
|
||||
} else if (req->param1 < PANDA_CAN_CNT) {
|
||||
print("Clearing CAN Tx queue\n");
|
||||
can_clear(can_queues[req->param1]);
|
||||
} else {
|
||||
@@ -225,7 +225,7 @@ int comms_control_handler(ControlPacket_t *req, uint8_t *resp) {
|
||||
break;
|
||||
// **** 0xf5: Set CAN silent mode
|
||||
case 0xf5:
|
||||
can_silent = (req->param1 > 0U) ? ALL_CAN_SILENT : ALL_CAN_LIVE;
|
||||
can_silent = (req->param1 > 0U);
|
||||
can_init_all();
|
||||
break;
|
||||
// **** 0xf7: enable/disable header pin by number
|
||||
|
||||
27
board/main.c
27
board/main.c
@@ -55,12 +55,12 @@ void set_safety_mode(uint16_t mode, uint16_t param) {
|
||||
case SAFETY_SILENT:
|
||||
set_intercept_relay(false, false);
|
||||
current_board->set_can_mode(CAN_MODE_NORMAL);
|
||||
can_silent = ALL_CAN_SILENT;
|
||||
can_silent = true;
|
||||
break;
|
||||
case SAFETY_NOOUTPUT:
|
||||
set_intercept_relay(false, false);
|
||||
current_board->set_can_mode(CAN_MODE_NORMAL);
|
||||
can_silent = ALL_CAN_LIVE;
|
||||
can_silent = false;
|
||||
break;
|
||||
case SAFETY_ELM327:
|
||||
set_intercept_relay(false, false);
|
||||
@@ -75,14 +75,14 @@ void set_safety_mode(uint16_t mode, uint16_t param) {
|
||||
} else {
|
||||
current_board->set_can_mode(CAN_MODE_NORMAL);
|
||||
}
|
||||
can_silent = ALL_CAN_LIVE;
|
||||
can_silent = false;
|
||||
break;
|
||||
default:
|
||||
set_intercept_relay(true, false);
|
||||
heartbeat_counter = 0U;
|
||||
heartbeat_lost = false;
|
||||
current_board->set_can_mode(CAN_MODE_NORMAL);
|
||||
can_silent = ALL_CAN_LIVE;
|
||||
can_silent = false;
|
||||
break;
|
||||
}
|
||||
can_init_all();
|
||||
@@ -118,6 +118,7 @@ static void tick_handler(void) {
|
||||
static uint32_t controls_allowed_countdown = 0;
|
||||
static uint8_t prev_harness_status = HARNESS_STATUS_NC;
|
||||
static uint8_t loop_counter = 0U;
|
||||
static bool relay_malfunction_prev = false;
|
||||
|
||||
if (TICK_TIMER->SR != 0U) {
|
||||
|
||||
@@ -130,6 +131,15 @@ static void tick_handler(void) {
|
||||
simple_watchdog_kick();
|
||||
sound_tick();
|
||||
|
||||
if (relay_malfunction_prev != relay_malfunction) {
|
||||
if (relay_malfunction) {
|
||||
fault_occurred(FAULT_RELAY_MALFUNCTION);
|
||||
} else {
|
||||
fault_recovered(FAULT_RELAY_MALFUNCTION);
|
||||
}
|
||||
}
|
||||
relay_malfunction_prev = relay_malfunction;
|
||||
|
||||
// re-init everything that uses harness status
|
||||
if (harness.status != prev_harness_status) {
|
||||
prev_harness_status = harness.status;
|
||||
@@ -143,14 +153,7 @@ static void tick_handler(void) {
|
||||
|
||||
// decimated to 1Hz
|
||||
if (loop_counter == 0U) {
|
||||
can_live = pending_can_live;
|
||||
|
||||
//puth(usart1_dma); print(" "); puth(DMA2_Stream5->M0AR); print(" "); puth(DMA2_Stream5->NDTR); print("\n");
|
||||
|
||||
// reset this every 16th pass
|
||||
if ((uptime_cnt & 0xFU) == 0U) {
|
||||
pending_can_live = 0;
|
||||
}
|
||||
#ifdef DEBUG
|
||||
print("** blink ");
|
||||
print("rx:"); puth4(can_rx_q.r_ptr); print("-"); puth4(can_rx_q.w_ptr); print(" ");
|
||||
@@ -303,7 +306,7 @@ int main(void) {
|
||||
microsecond_timer_init();
|
||||
|
||||
current_board->set_siren(false);
|
||||
if (current_board->fan_max_rpm > 0U) {
|
||||
if (current_board->has_fan) {
|
||||
fan_init();
|
||||
}
|
||||
|
||||
|
||||
@@ -210,13 +210,7 @@ int comms_control_handler(ControlPacket_t *req, uint8_t *resp) {
|
||||
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);
|
||||
}
|
||||
current_board->set_can_mode((req->param1 == 1U) ? CAN_MODE_OBD_CAN2 : CAN_MODE_NORMAL);
|
||||
break;
|
||||
// **** 0xdc: set safety mode
|
||||
case 0xdc:
|
||||
@@ -231,7 +225,7 @@ int comms_control_handler(ControlPacket_t *req, uint8_t *resp) {
|
||||
break;
|
||||
// **** 0xde: set can bitrate
|
||||
case 0xde:
|
||||
if ((req->param1 < PANDA_BUS_CNT) && is_speed_valid(req->param2, speeds, sizeof(speeds)/sizeof(speeds[0]))) {
|
||||
if ((req->param1 < PANDA_CAN_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);
|
||||
@@ -282,7 +276,7 @@ int comms_control_handler(ControlPacket_t *req, uint8_t *resp) {
|
||||
if (req->param1 == 0xFFFFU) {
|
||||
print("Clearing CAN Rx queue\n");
|
||||
can_clear(&can_rx_q);
|
||||
} else if (req->param1 < PANDA_BUS_CNT) {
|
||||
} else if (req->param1 < PANDA_CAN_CNT) {
|
||||
print("Clearing CAN Tx queue\n");
|
||||
can_clear(can_queues[req->param1]);
|
||||
} else {
|
||||
|
||||
@@ -104,33 +104,12 @@ void sound_init(void) {
|
||||
REGISTER_INTERRUPT(BDMA_Channel0_IRQn, BDMA_Channel0_IRQ_Handler, 128U, FAULT_INTERRUPT_RATE_SOUND_DMA)
|
||||
REGISTER_INTERRUPT(DMA1_Stream0_IRQn, DMA1_Stream0_IRQ_Handler, 128U, FAULT_INTERRUPT_RATE_SOUND_DMA)
|
||||
|
||||
// *** tmp, remove soon ***
|
||||
static const uint8_t olds[][12] = {
|
||||
{0x44, 0x00, 0x10, 0x00, 0x19, 0x51, 0x32, 0x34, 0x39, 0x37, 0x37, 0x30},
|
||||
{0x14, 0x00, 0x13, 0x00, 0x18, 0x51, 0x32, 0x34, 0x39, 0x37, 0x37, 0x30},
|
||||
{0x04, 0x00, 0x30, 0x00, 0x18, 0x51, 0x32, 0x34, 0x39, 0x37, 0x37, 0x30},
|
||||
{0x2f, 0x00, 0x14, 0x00, 0x18, 0x51, 0x32, 0x34, 0x39, 0x37, 0x37, 0x30},
|
||||
{0x1e, 0x00, 0x2f, 0x00, 0x18, 0x51, 0x32, 0x34, 0x39, 0x37, 0x37, 0x30},
|
||||
{0x26, 0x00, 0x15, 0x00, 0x19, 0x51, 0x32, 0x34, 0x39, 0x37, 0x37, 0x30},
|
||||
{0x35, 0x00, 0x32, 0x00, 0x18, 0x51, 0x32, 0x34, 0x39, 0x37, 0x37, 0x30},
|
||||
{0x37, 0x00, 0x2f, 0x00, 0x18, 0x51, 0x32, 0x34, 0x39, 0x37, 0x37, 0x30},
|
||||
};
|
||||
bool is_old = false;
|
||||
for (uint8_t i = 0U; i < (sizeof(olds) / sizeof(olds[0])); i++) {
|
||||
is_old |= (memcmp(olds[i], ((uint8_t *)UID_BASE), 12) == 0);
|
||||
}
|
||||
// *** tmp end ***
|
||||
|
||||
// Init DAC
|
||||
DAC1->DHR12R1 = (1UL << 11);
|
||||
if (!is_old) DAC1->DHR12R2 = (1UL << 11);
|
||||
DAC1->DHR12R2 = (1UL << 11);
|
||||
register_set(&DAC1->MCR, 0U, 0xFFFFFFFFU);
|
||||
register_set(&DAC1->CR, DAC_CR_TEN1 | (4U << DAC_CR_TSEL1_Pos) | DAC_CR_DMAEN1, 0xFFFFFFFFU);
|
||||
if (is_old) {
|
||||
register_set_bits(&DAC1->CR, DAC_CR_EN1);
|
||||
} else {
|
||||
register_set_bits(&DAC1->CR, DAC_CR_EN1 | DAC_CR_EN2);
|
||||
}
|
||||
register_set_bits(&DAC1->CR, DAC_CR_EN1 | DAC_CR_EN2);
|
||||
|
||||
// Setup DMAMUX (DAC_CH1_DMA as input)
|
||||
register_set(&DMAMUX1_Channel1->CCR, 67U, DMAMUX_CxCR_DMAREQ_ID_Msk);
|
||||
|
||||
@@ -30,6 +30,7 @@ dev = [
|
||||
"mypy",
|
||||
"setuptools",
|
||||
"spidev; platform_system == 'Linux'",
|
||||
"spidev2; platform_system == 'Linux'",
|
||||
]
|
||||
|
||||
[build-system]
|
||||
|
||||
@@ -23,7 +23,7 @@ __version__ = '0.0.10'
|
||||
CANPACKET_HEAD_SIZE = 0x6
|
||||
DLC_TO_LEN = [0, 1, 2, 3, 4, 5, 6, 7, 8, 12, 16, 20, 24, 32, 48, 64]
|
||||
LEN_TO_DLC = {length: dlc for (dlc, length) in enumerate(DLC_TO_LEN)}
|
||||
PANDA_BUS_CNT = 3
|
||||
PANDA_CAN_CNT = 3
|
||||
|
||||
|
||||
def calculate_checksum(data):
|
||||
@@ -32,16 +32,13 @@ def calculate_checksum(data):
|
||||
res ^= b
|
||||
return res
|
||||
|
||||
def pack_can_buffer(arr, fd=False):
|
||||
snds = [b'']
|
||||
def pack_can_buffer(arr, chunk=False, fd=False):
|
||||
snds = [bytearray(), ]
|
||||
for address, dat, bus in arr:
|
||||
assert len(dat) in LEN_TO_DLC
|
||||
#logger.debug(" W 0x%x: 0x%s", address, dat.hex())
|
||||
|
||||
extended = 1 if address >= 0x800 else 0
|
||||
data_len_code = LEN_TO_DLC[len(dat)]
|
||||
header = bytearray(CANPACKET_HEAD_SIZE)
|
||||
word_4b = address << 3 | extended << 2
|
||||
word_4b = (address << 3) | (extended << 2)
|
||||
header[0] = (data_len_code << 4) | (bus << 1) | int(fd)
|
||||
header[1] = word_4b & 0xFF
|
||||
header[2] = (word_4b >> 8) & 0xFF
|
||||
@@ -49,9 +46,10 @@ def pack_can_buffer(arr, fd=False):
|
||||
header[4] = (word_4b >> 24) & 0xFF
|
||||
header[5] = calculate_checksum(header[:5] + dat)
|
||||
|
||||
snds[-1] += header + dat
|
||||
if len(snds[-1]) > 256: # Limit chunks to 256 bytes
|
||||
snds.append(b'')
|
||||
snds[-1].extend(header)
|
||||
snds[-1].extend(dat)
|
||||
if chunk and len(snds[-1]) > 256:
|
||||
snds.append(bytearray())
|
||||
|
||||
return snds
|
||||
|
||||
@@ -133,7 +131,7 @@ class Panda:
|
||||
|
||||
MAX_FAN_RPMs = {
|
||||
HW_TYPE_TRES: 6600,
|
||||
HW_TYPE_CUATRO: 12500,
|
||||
HW_TYPE_CUATRO: 5000,
|
||||
}
|
||||
|
||||
HARNESS_STATUS_NC = 0
|
||||
@@ -198,9 +196,9 @@ class Panda:
|
||||
self._handle = None
|
||||
while self._handle is None:
|
||||
# try USB first, then SPI
|
||||
self._context, self._handle, serial, self.bootstub, bcd = self.usb_connect(self._connect_serial, claim=claim, no_error=wait)
|
||||
self._context, self._handle, serial, self.bootstub = self.usb_connect(self._connect_serial, claim=claim, no_error=wait)
|
||||
if self._handle is None:
|
||||
self._context, self._handle, serial, self.bootstub, bcd = self.spi_connect(self._connect_serial)
|
||||
self._context, self._handle, serial, self.bootstub = self.spi_connect(self._connect_serial)
|
||||
if not wait:
|
||||
break
|
||||
|
||||
@@ -227,11 +225,11 @@ class Panda:
|
||||
self.can_reset_communications()
|
||||
|
||||
# disable automatic CAN-FD switching
|
||||
for bus in range(PANDA_BUS_CNT):
|
||||
for bus in range(PANDA_CAN_CNT):
|
||||
self.set_canfd_auto(bus, False)
|
||||
|
||||
# set CAN speed
|
||||
for bus in range(PANDA_BUS_CNT):
|
||||
for bus in range(PANDA_CAN_CNT):
|
||||
self.set_can_speed_kbps(bus, self._can_speed_kbps)
|
||||
|
||||
@property
|
||||
@@ -240,49 +238,33 @@ class Panda:
|
||||
|
||||
@classmethod
|
||||
def spi_connect(cls, serial, ignore_version=False):
|
||||
# get UID to confirm slave is present and up
|
||||
handle = None
|
||||
spi_serial = None
|
||||
bootstub = None
|
||||
spi_version = None
|
||||
try:
|
||||
handle = PandaSpiHandle()
|
||||
|
||||
# connect by protcol version
|
||||
try:
|
||||
dat = handle.get_protocol_version()
|
||||
spi_serial = binascii.hexlify(dat[:12]).decode()
|
||||
pid = dat[13]
|
||||
if pid not in (0xcc, 0xee):
|
||||
raise PandaSpiException("invalid bootstub status")
|
||||
bootstub = pid == 0xee
|
||||
spi_version = dat[14]
|
||||
except PandaSpiException:
|
||||
# fallback, we'll raise a protocol mismatch below
|
||||
dat = handle.controlRead(Panda.REQUEST_IN, 0xc3, 0, 0, 12, timeout=100)
|
||||
spi_serial = binascii.hexlify(dat).decode()
|
||||
bootstub = Panda.flasher_present(handle)
|
||||
spi_version = 0
|
||||
dat = handle.get_protocol_version()
|
||||
except PandaSpiException:
|
||||
pass
|
||||
return None, None, None, False
|
||||
|
||||
# no connection or wrong panda
|
||||
if None in (spi_serial, bootstub) or (serial is not None and (spi_serial != serial)):
|
||||
handle = None
|
||||
spi_serial = None
|
||||
bootstub = False
|
||||
spi_serial = binascii.hexlify(dat[:12]).decode()
|
||||
pid = dat[13]
|
||||
if pid not in (0xcc, 0xee):
|
||||
raise PandaProtocolMismatch(f"invalid bootstub status ({pid=}). reflash panda")
|
||||
bootstub = pid == 0xee
|
||||
spi_version = dat[14]
|
||||
|
||||
# did we get the right panda?
|
||||
if serial is not None and spi_serial != serial:
|
||||
return None, None, None, False
|
||||
|
||||
# ensure our protocol version matches the panda
|
||||
if handle is not None and not ignore_version:
|
||||
if spi_version != handle.PROTOCOL_VERSION:
|
||||
err = f"panda protocol mismatch: expected {handle.PROTOCOL_VERSION}, got {spi_version}. reflash panda"
|
||||
raise PandaProtocolMismatch(err)
|
||||
if (not ignore_version) and spi_version != handle.PROTOCOL_VERSION:
|
||||
raise PandaProtocolMismatch(f"panda protocol mismatch: expected {handle.PROTOCOL_VERSION}, got {spi_version}. reflash panda")
|
||||
|
||||
return None, handle, spi_serial, bootstub, None
|
||||
# got a device and all good
|
||||
return None, handle, spi_serial, bootstub
|
||||
|
||||
@classmethod
|
||||
def usb_connect(cls, serial, claim=True, no_error=False):
|
||||
handle, usb_serial, bootstub, bcd = None, None, None, None
|
||||
handle, usb_serial, bootstub = None, None, None
|
||||
context = usb1.USBContext()
|
||||
context.open()
|
||||
try:
|
||||
@@ -308,11 +290,6 @@ class Panda:
|
||||
handle.claimInterface(0)
|
||||
# handle.setInterfaceAltSetting(0, 0) # Issue in USB stack
|
||||
|
||||
# bcdDevice wasn't always set to the hw type, ignore if it's the old constant
|
||||
this_bcd = device.getbcdDevice()
|
||||
if this_bcd is not None and this_bcd != 0x2300:
|
||||
bcd = bytearray([this_bcd >> 8, ])
|
||||
|
||||
break
|
||||
except Exception:
|
||||
logger.exception("USB connect error")
|
||||
@@ -323,7 +300,7 @@ class Panda:
|
||||
else:
|
||||
context.close()
|
||||
|
||||
return context, usb_handle, usb_serial, bootstub, bcd
|
||||
return context, usb_handle, usb_serial, bootstub
|
||||
|
||||
def is_connected_spi(self):
|
||||
return isinstance(self._handle, PandaSpiHandle)
|
||||
@@ -359,7 +336,7 @@ class Panda:
|
||||
|
||||
@classmethod
|
||||
def spi_list(cls):
|
||||
_, _, serial, _, _ = cls.spi_connect(None, ignore_version=True)
|
||||
_, _, serial, _ = cls.spi_connect(None, ignore_version=True)
|
||||
if serial is not None:
|
||||
return [serial, ]
|
||||
return []
|
||||
@@ -731,7 +708,7 @@ class Panda:
|
||||
|
||||
@ensure_can_packet_version
|
||||
def can_send_many(self, arr, *, fd=False, timeout=CAN_SEND_TIMEOUT_MS):
|
||||
snds = pack_can_buffer(arr, fd=fd)
|
||||
snds = pack_can_buffer(arr, chunk=(not self.spi), fd=fd)
|
||||
for tx in snds:
|
||||
while len(tx) > 0:
|
||||
bs = self._handle.bulkWrite(3, tx, timeout=timeout)
|
||||
|
||||
133
python/spi.py
133
python/spi.py
@@ -1,5 +1,4 @@
|
||||
import binascii
|
||||
import ctypes
|
||||
import os
|
||||
import fcntl
|
||||
import math
|
||||
@@ -8,7 +7,6 @@ import struct
|
||||
import threading
|
||||
from contextlib import contextmanager
|
||||
from functools import reduce
|
||||
from collections.abc import Callable
|
||||
|
||||
from .base import BaseHandle, BaseSTBootloaderHandle, TIMEOUT
|
||||
from .constants import McuType, MCU_TYPE_BY_IDCODE, USBPACKET_MAX_SIZE
|
||||
@@ -18,6 +16,10 @@ try:
|
||||
import spidev
|
||||
except ImportError:
|
||||
spidev = None
|
||||
try:
|
||||
import spidev2
|
||||
except ImportError:
|
||||
spidev2 = None
|
||||
|
||||
# Constants
|
||||
SYNC = 0x5A
|
||||
@@ -29,7 +31,8 @@ CHECKSUM_START = 0xAB
|
||||
MIN_ACK_TIMEOUT_MS = 100
|
||||
MAX_XFER_RETRY_COUNT = 5
|
||||
|
||||
XFER_SIZE = 0x40*31
|
||||
SPI_BUF_SIZE = 4096 # from panda/board/drivers/spi.h
|
||||
XFER_SIZE = SPI_BUF_SIZE - 0x40 # give some room for SPI protocol overhead
|
||||
|
||||
DEV_PATH = "/dev/spidev0.0"
|
||||
|
||||
@@ -70,18 +73,6 @@ class PandaSpiTransferFailed(PandaSpiException):
|
||||
pass
|
||||
|
||||
|
||||
class PandaSpiTransfer(ctypes.Structure):
|
||||
_fields_ = [
|
||||
('rx_buf', ctypes.c_uint64),
|
||||
('tx_buf', ctypes.c_uint64),
|
||||
('tx_length', ctypes.c_uint32),
|
||||
('rx_length_max', ctypes.c_uint32),
|
||||
('timeout', ctypes.c_uint32),
|
||||
('endpoint', ctypes.c_uint8),
|
||||
('expect_disconnect', ctypes.c_uint8),
|
||||
]
|
||||
|
||||
|
||||
SPI_LOCK = threading.Lock()
|
||||
SPI_DEVICES = {}
|
||||
class SpiDevice:
|
||||
@@ -89,9 +80,7 @@ class SpiDevice:
|
||||
Provides locked, thread-safe access to a panda's SPI interface.
|
||||
"""
|
||||
|
||||
# 50MHz is the max of the 845. older rev comma three
|
||||
# may not support the full 50MHz
|
||||
MAX_SPEED = 50000000
|
||||
MAX_SPEED = 50000000 # max of the SDM845
|
||||
|
||||
def __init__(self, speed=MAX_SPEED):
|
||||
assert speed <= self.MAX_SPEED
|
||||
@@ -128,24 +117,12 @@ class PandaSpiHandle(BaseHandle):
|
||||
"""
|
||||
|
||||
PROTOCOL_VERSION = 2
|
||||
HEADER = struct.Struct("<BBHH")
|
||||
|
||||
def __init__(self) -> None:
|
||||
self.dev = SpiDevice()
|
||||
|
||||
self._transfer_raw: Callable[[SpiDevice, int, bytes, int, int, bool], bytes] = self._transfer_spidev
|
||||
|
||||
if "KERN" in os.environ:
|
||||
self._transfer_raw = self._transfer_kernel_driver
|
||||
|
||||
self.tx_buf = bytearray(1024)
|
||||
self.rx_buf = bytearray(1024)
|
||||
tx_buf_raw = ctypes.c_char.from_buffer(self.tx_buf)
|
||||
rx_buf_raw = ctypes.c_char.from_buffer(self.rx_buf)
|
||||
|
||||
self.ioctl_data = PandaSpiTransfer()
|
||||
self.ioctl_data.tx_buf = ctypes.addressof(tx_buf_raw)
|
||||
self.ioctl_data.rx_buf = ctypes.addressof(rx_buf_raw)
|
||||
self.fileno = self.dev._spidev.fileno()
|
||||
if spidev2 is not None and "SPI2" in os.environ:
|
||||
self._spi2 = spidev2.SPIBus("/dev/spidev0.0", "w+b", bits_per_word=8, speed_hz=50_000_000)
|
||||
|
||||
# helpers
|
||||
def _calc_checksum(self, data: bytes) -> int:
|
||||
@@ -160,10 +137,10 @@ class PandaSpiHandle(BaseHandle):
|
||||
start = time.monotonic()
|
||||
while (timeout == 0) or ((time.monotonic() - start) < timeout_s):
|
||||
dat = spi.xfer2([tx, ] * length)
|
||||
if dat[0] == NACK:
|
||||
raise PandaSpiNackResponse
|
||||
elif dat[0] == ack_val:
|
||||
if dat[0] == ack_val:
|
||||
return bytes(dat)
|
||||
elif dat[0] == NACK:
|
||||
raise PandaSpiNackResponse
|
||||
|
||||
raise PandaSpiMissingAck
|
||||
|
||||
@@ -171,7 +148,7 @@ class PandaSpiHandle(BaseHandle):
|
||||
max_rx_len = max(USBPACKET_MAX_SIZE, max_rx_len)
|
||||
|
||||
logger.debug("- send header")
|
||||
packet = struct.pack("<BBHH", SYNC, endpoint, len(data), max_rx_len)
|
||||
packet = self.HEADER.pack(SYNC, endpoint, len(data), max_rx_len)
|
||||
packet += bytes([self._calc_checksum(packet), ])
|
||||
spi.xfer2(packet)
|
||||
|
||||
@@ -200,29 +177,57 @@ class PandaSpiHandle(BaseHandle):
|
||||
if remaining > 0:
|
||||
dat += bytes(spi.readbytes(remaining))
|
||||
|
||||
|
||||
dat = dat[:3 + response_len + 1]
|
||||
if self._calc_checksum(dat) != 0:
|
||||
raise PandaSpiBadChecksum
|
||||
|
||||
return dat[3:-1]
|
||||
|
||||
def _transfer_kernel_driver(self, spi, endpoint: int, data, timeout: int, max_rx_len: int = 1000, expect_disconnect: bool = False) -> bytes:
|
||||
import spidev2
|
||||
self.tx_buf[:len(data)] = data
|
||||
self.ioctl_data.endpoint = endpoint
|
||||
self.ioctl_data.tx_length = len(data)
|
||||
self.ioctl_data.rx_length_max = max_rx_len
|
||||
self.ioctl_data.expect_disconnect = int(expect_disconnect)
|
||||
def _transfer_spidev2(self, spi, endpoint: int, data, timeout: int, max_rx_len: int = USBPACKET_MAX_SIZE, expect_disconnect: bool = False) -> bytes:
|
||||
max_rx_len = max(USBPACKET_MAX_SIZE, max_rx_len)
|
||||
|
||||
# TODO: use our own ioctl request
|
||||
try:
|
||||
ret = fcntl.ioctl(self.fileno, spidev2.SPI_IOC_RD_LSB_FIRST, self.ioctl_data)
|
||||
except OSError as e:
|
||||
raise PandaSpiException from e
|
||||
if ret < 0:
|
||||
raise PandaSpiException(f"ioctl returned {ret}")
|
||||
return bytes(self.rx_buf[:ret])
|
||||
header = self.HEADER.pack(SYNC, endpoint, len(data), max_rx_len)
|
||||
|
||||
header_ack = bytearray(1)
|
||||
|
||||
# ACK + <2 bytes for response length> + data + checksum
|
||||
data_rx = bytearray(3+max_rx_len+1)
|
||||
|
||||
self._spi2.submitTransferList(spidev2.SPITransferList((
|
||||
# header
|
||||
{'tx_buf': header + bytes([self._calc_checksum(header), ]), 'delay_usecs': 0, 'cs_change': True},
|
||||
{'rx_buf': header_ack, 'delay_usecs': 0, 'cs_change': True},
|
||||
|
||||
# send data
|
||||
{'tx_buf': bytes([*data, self._calc_checksum(data)]), 'delay_usecs': 0, 'cs_change': True},
|
||||
{'rx_buf': data_rx, 'delay_usecs': 0, 'cs_change': True},
|
||||
)))
|
||||
|
||||
if header_ack[0] != HACK:
|
||||
raise PandaSpiMissingAck
|
||||
|
||||
if expect_disconnect:
|
||||
logger.debug("- expecting disconnect, returning")
|
||||
return b""
|
||||
else:
|
||||
dat = bytes(data_rx)
|
||||
if dat[0] != DACK:
|
||||
if dat[0] == NACK:
|
||||
raise PandaSpiNackResponse
|
||||
|
||||
print("trying again")
|
||||
dat = self._wait_for_ack(spi, DACK, timeout, 0x13, length=3 + max_rx_len)
|
||||
|
||||
# get response length, then response
|
||||
response_len = struct.unpack("<H", dat[1:3])[0]
|
||||
if response_len > max_rx_len:
|
||||
raise PandaSpiException(f"response length greater than max ({max_rx_len} {response_len})")
|
||||
|
||||
dat = dat[:3 + response_len + 1]
|
||||
if self._calc_checksum(dat) != 0:
|
||||
raise PandaSpiBadChecksum
|
||||
|
||||
return dat[3:-1]
|
||||
|
||||
def _transfer(self, endpoint: int, data, timeout: int, max_rx_len: int = 1000, expect_disconnect: bool = False) -> bytes:
|
||||
logger.debug("starting transfer: endpoint=%d, max_rx_len=%d", endpoint, max_rx_len)
|
||||
@@ -236,11 +241,25 @@ class PandaSpiHandle(BaseHandle):
|
||||
logger.debug("\ntry #%d", n)
|
||||
with self.dev.acquire() as spi:
|
||||
try:
|
||||
return self._transfer_raw(spi, endpoint, data, timeout, max_rx_len, expect_disconnect)
|
||||
fn = self._transfer_spidev
|
||||
#fn = self._transfer_spidev2
|
||||
return fn(spi, endpoint, data, timeout, max_rx_len, expect_disconnect)
|
||||
except PandaSpiException as e:
|
||||
exc = e
|
||||
logger.debug("SPI transfer failed, retrying", exc_info=True)
|
||||
|
||||
# ensure slave is in a consistent state and ready for the next transfer
|
||||
# (e.g. slave TX buffer isn't stuck full)
|
||||
nack_cnt = 0
|
||||
attempts = 5
|
||||
while (nack_cnt <= 3) and (attempts > 0):
|
||||
attempts -= 1
|
||||
try:
|
||||
self._wait_for_ack(spi, NACK, MIN_ACK_TIMEOUT_MS, 0x11, length=XFER_SIZE//2)
|
||||
nack_cnt += 1
|
||||
except PandaSpiException:
|
||||
nack_cnt = 0
|
||||
|
||||
raise exc
|
||||
|
||||
def get_protocol_version(self) -> bytes:
|
||||
@@ -290,8 +309,9 @@ class PandaSpiHandle(BaseHandle):
|
||||
return self._transfer(0, struct.pack("<BHHH", request, value, index, length), timeout, max_rx_len=length)
|
||||
|
||||
def bulkWrite(self, endpoint: int, data: bytes, timeout: int = TIMEOUT) -> int:
|
||||
mv = memoryview(data)
|
||||
for x in range(math.ceil(len(data) / XFER_SIZE)):
|
||||
self._transfer(endpoint, data[XFER_SIZE*x:XFER_SIZE*(x+1)], timeout)
|
||||
self._transfer(endpoint, mv[XFER_SIZE*x:XFER_SIZE*(x+1)], timeout)
|
||||
return len(data)
|
||||
|
||||
def bulkRead(self, endpoint: int, length: int, timeout: int = TIMEOUT) -> bytes:
|
||||
@@ -308,6 +328,9 @@ class STBootloaderSPIHandle(BaseSTBootloaderHandle):
|
||||
"""
|
||||
Implementation of the STM32 SPI bootloader protocol described in:
|
||||
https://www.st.com/resource/en/application_note/an4286-spi-protocol-used-in-the-stm32-bootloader-stmicroelectronics.pdf
|
||||
|
||||
NOTE: the bootloader's state machine is fragile and immediately gets into a bad state when
|
||||
sending any junk, e.g. when using the panda SPI protocol.
|
||||
"""
|
||||
|
||||
SYNC = 0x5A
|
||||
|
||||
@@ -1,17 +1,32 @@
|
||||
#!/usr/bin/env python3
|
||||
import io
|
||||
import os
|
||||
import time
|
||||
import pstats
|
||||
import cProfile
|
||||
from contextlib import contextmanager
|
||||
|
||||
from panda import Panda, PandaDFU
|
||||
from panda.tests.hitl.helpers import get_random_can_messages
|
||||
|
||||
|
||||
PROFILE = "PROFILE" in os.environ
|
||||
|
||||
@contextmanager
|
||||
def print_time(desc):
|
||||
if PROFILE:
|
||||
pr = cProfile.Profile()
|
||||
pr.enable()
|
||||
start = time.perf_counter()
|
||||
yield
|
||||
end = time.perf_counter()
|
||||
print(f"{end - start:.2f}s - {desc}")
|
||||
print(f"{end - start:.3f}s - {desc}")
|
||||
if PROFILE:
|
||||
pr.disable()
|
||||
s = io.StringIO()
|
||||
ps = pstats.Stats(pr, stream=s).sort_stats("cumtime")
|
||||
ps.print_stats()
|
||||
print(s.getvalue())
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
|
||||
2
setup.sh
2
setup.sh
@@ -38,5 +38,5 @@ if ! command -v uv &>/dev/null; then
|
||||
fi
|
||||
|
||||
export UV_PROJECT_ENVIRONMENT="$DIR/.venv"
|
||||
uv sync --all-extras
|
||||
uv sync --all-extras --upgrade
|
||||
source "$DIR/.venv/bin/activate"
|
||||
|
||||
@@ -3,31 +3,9 @@ import pytest
|
||||
import random
|
||||
from unittest.mock import patch
|
||||
|
||||
from panda import Panda, PandaDFU
|
||||
from panda.python.spi import SpiDevice, PandaProtocolMismatch, PandaSpiNackResponse
|
||||
from panda import Panda
|
||||
from panda.python.spi import PandaProtocolMismatch, PandaSpiNackResponse
|
||||
|
||||
pytestmark = [
|
||||
pytest.mark.test_panda_types((Panda.HW_TYPE_TRES, ))
|
||||
]
|
||||
|
||||
@pytest.mark.skip("doesn't work, bootloader seems to ignore commands once it sees junk")
|
||||
def test_dfu_with_spam(p):
|
||||
dfu_serial = p.get_dfu_serial()
|
||||
|
||||
# enter DFU
|
||||
p.reset(enter_bootstub=True)
|
||||
p.reset(enter_bootloader=True)
|
||||
assert Panda.wait_for_dfu(dfu_serial, timeout=19), "failed to enter DFU"
|
||||
|
||||
# send junk
|
||||
d = SpiDevice()
|
||||
for _ in range(9):
|
||||
with d.acquire() as spi:
|
||||
dat = [random.randint(-1, 255) for _ in range(random.randint(1, 100))]
|
||||
spi.xfer(dat)
|
||||
|
||||
# should still show up
|
||||
assert dfu_serial in PandaDFU.list()
|
||||
|
||||
class TestSpi:
|
||||
def _ping(self, mocker, panda):
|
||||
|
||||
@@ -4,18 +4,20 @@ import pytest
|
||||
from panda import Panda
|
||||
|
||||
pytestmark = [
|
||||
pytest.mark.test_panda_types(Panda.INTERNAL_DEVICES)
|
||||
pytest.mark.test_panda_types(Panda.INTERNAL_DEVICES),
|
||||
pytest.mark.test_panda_types([Panda.HW_TYPE_TRES])
|
||||
]
|
||||
|
||||
@pytest.mark.timeout(2*60)
|
||||
def test_fan_controller(p):
|
||||
def test_fan_curve(p):
|
||||
# ensure fan curve is (roughly) linear
|
||||
|
||||
for power in (30, 50, 80, 100):
|
||||
p.set_fan_power(0)
|
||||
while p.get_fan_rpm() > 0:
|
||||
time.sleep(0.1)
|
||||
|
||||
# wait until fan spins up (and recovers if needed),
|
||||
# then wait a bit more for the RPM to converge
|
||||
# wait until fan spins up, then wait a bit more for the RPM to converge
|
||||
p.set_fan_power(power)
|
||||
for _ in range(20):
|
||||
time.sleep(1)
|
||||
@@ -24,7 +26,7 @@ def test_fan_controller(p):
|
||||
time.sleep(5)
|
||||
|
||||
expected_rpm = Panda.MAX_FAN_RPMs[bytes(p.get_type())] * power / 100
|
||||
assert 0.9 * expected_rpm <= p.get_fan_rpm() <= 1.1 * expected_rpm
|
||||
assert 0.75 * expected_rpm <= p.get_fan_rpm() <= 1.25 * expected_rpm
|
||||
|
||||
def test_fan_cooldown(p):
|
||||
# if the fan cooldown doesn't work, we get high frequency noise on the tach line
|
||||
@@ -35,21 +37,3 @@ def test_fan_cooldown(p):
|
||||
for _ in range(5):
|
||||
assert p.get_fan_rpm() <= Panda.MAX_FAN_RPMs[bytes(p.get_type())]
|
||||
time.sleep(0.5)
|
||||
|
||||
def test_fan_overshoot(p):
|
||||
|
||||
# make sure it's stopped completely
|
||||
p.set_fan_power(0)
|
||||
while p.get_fan_rpm() > 0:
|
||||
time.sleep(0.1)
|
||||
|
||||
# set it to 30% power to mimic going onroad
|
||||
p.set_fan_power(30)
|
||||
max_rpm = 0
|
||||
for _ in range(50):
|
||||
max_rpm = max(max_rpm, p.get_fan_rpm())
|
||||
time.sleep(0.1)
|
||||
|
||||
# tolerate 10% overshoot
|
||||
expected_rpm = Panda.MAX_FAN_RPMs[bytes(p.get_type())] * 30 / 100
|
||||
assert max_rpm <= 1.1 * expected_rpm, f"Fan overshoot: {(max_rpm / expected_rpm * 100) - 100:.1f}%"
|
||||
|
||||
@@ -21,7 +21,7 @@ env = Environment(
|
||||
'-Wfatal-errors',
|
||||
'-Wno-pointer-to-int-cast',
|
||||
],
|
||||
CPPPATH=[".", "../../board/", opendbc.INCLUDE_PATH],
|
||||
CPPPATH=[".", "../../", "../../board/", opendbc.INCLUDE_PATH],
|
||||
)
|
||||
if system == "Darwin":
|
||||
env.PrependENVPath('PATH', '/opt/homebrew/bin')
|
||||
|
||||
@@ -53,7 +53,7 @@ patterns = [
|
||||
|
||||
all_files = glob.glob('board/**', root_dir=ROOT, recursive=True)
|
||||
files = [f for f in all_files if f.endswith(('.c', '.h')) and not f.startswith(IGNORED_PATHS)]
|
||||
assert len(files) > 70, all(d in files for d in ('board/main.c', 'board/stm32h7/llfdcan.h'))
|
||||
assert len(files) > 50, all(d in files for d in ('board/main.c', 'board/stm32h7/llfdcan.h'))
|
||||
|
||||
for p in patterns:
|
||||
mutations.append((random.choice(files), p, True))
|
||||
|
||||
@@ -78,17 +78,17 @@ class TestPandaComms(unittest.TestCase):
|
||||
def test_comms_reset_tx(self):
|
||||
# store some test messages in the queue
|
||||
test_msg = (0x100, b"test", 0)
|
||||
packed = pack_can_buffer([test_msg for _ in range(100)])
|
||||
packed = pack_can_buffer([test_msg for _ in range(100)], chunk=True)
|
||||
|
||||
# write a small chunk such that we have some overflow
|
||||
TINY_CHUNK_SIZE = 6
|
||||
lpp.comms_can_write(packed[0][:TINY_CHUNK_SIZE], TINY_CHUNK_SIZE)
|
||||
lpp.comms_can_write(bytes(packed[0][:TINY_CHUNK_SIZE]), TINY_CHUNK_SIZE)
|
||||
|
||||
# reset the comms to clear the overflow buffer on the panda side
|
||||
lpp.comms_can_reset()
|
||||
|
||||
# write a full valid chunk, which should now contain valid messages
|
||||
lpp.comms_can_write(packed[1], len(packed[1]))
|
||||
lpp.comms_can_write(bytes(packed[1]), len(packed[1]))
|
||||
|
||||
# read the messages from the queue and make sure they're valid
|
||||
queue_msgs = []
|
||||
@@ -114,7 +114,7 @@ class TestPandaComms(unittest.TestCase):
|
||||
for buf in packed:
|
||||
for i in range(0, len(buf), CHUNK_SIZE):
|
||||
chunk_len = min(CHUNK_SIZE, len(buf) - i)
|
||||
lpp.comms_can_write(buf[i:i+chunk_len], chunk_len)
|
||||
lpp.comms_can_write(bytes(buf[i:i+chunk_len]), chunk_len)
|
||||
|
||||
# Check that they ended up in the right buffers
|
||||
queue_msgs = []
|
||||
|
||||
Reference in New Issue
Block a user