This commit is contained in:
infiniteCable2
2025-11-16 13:54:29 +01:00
34 changed files with 218 additions and 367 deletions

7
Jenkinsfile vendored
View File

@@ -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"],
])
}
}

View File

@@ -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;

View File

@@ -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,

View File

@@ -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,

View File

@@ -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,

View File

@@ -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"

View File

@@ -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)

View File

@@ -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));

View File

@@ -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)

View File

@@ -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));
}
}

View File

@@ -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;

View File

@@ -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
}

View File

@@ -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);

View File

@@ -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) {

View File

@@ -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

View File

@@ -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;
}
}

View File

@@ -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];

View File

@@ -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)

View File

@@ -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:

View File

@@ -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();

View File

@@ -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

View File

@@ -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();
}

View File

@@ -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 {

View File

@@ -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);

View File

@@ -30,6 +30,7 @@ dev = [
"mypy",
"setuptools",
"spidev; platform_system == 'Linux'",
"spidev2; platform_system == 'Linux'",
]
[build-system]

View File

@@ -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)

View File

@@ -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

View File

@@ -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__":

View File

@@ -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"

View File

@@ -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):

View File

@@ -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}%"

View File

@@ -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')

View File

@@ -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))

View File

@@ -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 = []