Skip to content

Commit 54f4311

Browse files
committed
PCU/BMS: fix BMS pack voltage/temp on PCU; use IVT for RMS limiting
The BMS never transmitted the accumulator voltage (0x02) or temperature (0x03) frames, so the PCU read them as 0.0 and the RMS limiter falsely tripped the low-pack-voltage torque cut. The PCU also hand-parsed those frames with the wrong endianness/signal. - BMS: broadcast 0x02 (cell-sum pack voltage, decivolts) and 0x03 (max cell temperature, deci-C) from the existing 100ms state publish tick, using the generated pack functions. - PCU: decode 0x02/0x03 with the generated unpack functions for the bms console display (cell-sum voltage + max temperature). - PCU: add a local IVT CAN handler (generated unpack) and route the RMS torque/power limiter to measured IVT voltage + current, freshness-gated with fallback to the inverter DC bus voltage.
1 parent 8bdff09 commit 54f4311

11 files changed

Lines changed: 323 additions & 27 deletions

File tree

BMS/Core/User/Src/FEB_CAN_State.c

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,9 +6,11 @@
66
#include "FEB_CAN_State.h"
77
#include "FEB_CAN_DASH.h"
88
#include "FEB_SM.h"
9+
#include "FEB_ADBMS6830B.h"
910
#include "feb_can_lib.h"
1011
#include "feb_can.h"
1112
#include "stm32f4xx_hal.h"
13+
#include <math.h>
1214
#include <stdbool.h>
1315
#include <string.h>
1416

@@ -26,6 +28,12 @@ static volatile BMS_State_t current_state = BMS_STATE_BOOT;
2628
/* BMS state message data */
2729
static struct feb_can_bms_state_t bms_state_msg;
2830

31+
/* Accumulator telemetry messages broadcast to the PCU (consumed by FEB_CAN_BMS
32+
* on the PCU for the `bms` console display). total_pack_voltage is the cell-sum
33+
* pack voltage in decivolts; max_cell_temperature is in deci-degrees C. */
34+
static struct feb_can_bms_accumulator_voltage_t acc_voltage_msg;
35+
static struct feb_can_bms_accumulator_temperature_t acc_temp_msg;
36+
2937
/* State name lookup table - must match BMS_State_t enum order */
3038
static const char *state_names[] = {
3139
"BOOT", // 0
@@ -47,6 +55,8 @@ static const char *state_names[] = {
4755
void FEB_CAN_State_Init(void)
4856
{
4957
memset(&bms_state_msg, 0, sizeof(bms_state_msg));
58+
memset(&acc_voltage_msg, 0, sizeof(acc_voltage_msg));
59+
memset(&acc_temp_msg, 0, sizeof(acc_temp_msg));
5060
current_state = BMS_STATE_BOOT;
5161
}
5262

@@ -103,6 +113,25 @@ void FEB_CAN_State_Tick(void)
103113
feb_can_bms_state_pack(tx_data, &bms_state_msg, sizeof(tx_data));
104114

105115
FEB_CAN_TX_Send(FEB_CAN_INSTANCE_1, FEB_CAN_BMS_STATE_FRAME_ID, FEB_CAN_ID_STD, tx_data, FEB_CAN_BMS_STATE_LENGTH);
116+
117+
/* Accumulator voltage (0x02): total_pack_voltage = cell-sum in decivolts.
118+
* The PCU divides by 10 for display and uses it for the `bms` console view. */
119+
acc_voltage_msg.total_pack_voltage = (uint16_t)(FEB_ADBMS_Snapshot_Total_Voltage() * 10.0f + 0.5f);
120+
121+
uint8_t acc_v_data[FEB_CAN_BMS_ACCUMULATOR_VOLTAGE_LENGTH];
122+
feb_can_bms_accumulator_voltage_pack(acc_v_data, &acc_voltage_msg, sizeof(acc_v_data));
123+
FEB_CAN_TX_Send(FEB_CAN_INSTANCE_1, FEB_CAN_BMS_ACCUMULATOR_VOLTAGE_FRAME_ID, FEB_CAN_ID_STD, acc_v_data,
124+
FEB_CAN_BMS_ACCUMULATOR_VOLTAGE_LENGTH);
125+
126+
/* Accumulator temperature (0x03): max_cell_temperature in deci-degrees C.
127+
* Snapshot is NaN until the first cell-monitor scan completes. */
128+
float max_temp_c = FEB_ADBMS_Snapshot_Max_Temp();
129+
acc_temp_msg.max_cell_temperature = isnan(max_temp_c) ? 0 : (int16_t)(max_temp_c * 10.0f);
130+
131+
uint8_t acc_t_data[FEB_CAN_BMS_ACCUMULATOR_TEMPERATURE_LENGTH];
132+
feb_can_bms_accumulator_temperature_pack(acc_t_data, &acc_temp_msg, sizeof(acc_t_data));
133+
FEB_CAN_TX_Send(FEB_CAN_INSTANCE_1, FEB_CAN_BMS_ACCUMULATOR_TEMPERATURE_FRAME_ID, FEB_CAN_ID_STD, acc_t_data,
134+
FEB_CAN_BMS_ACCUMULATOR_TEMPERATURE_LENGTH);
106135
}
107136
}
108137

BMS/VERSION

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1 @@
1-
1.8.1
1+
1.8.2

PCU/Core/User/Inc/FEB_CAN_IVT.h

Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,57 @@
1+
/**
2+
* @file FEB_CAN_IVT.h
3+
* @brief IVT (Isabellenhutte) Current/Voltage sensor CAN reception for the PCU
4+
* @author Formula Electric @ Berkeley
5+
*
6+
* The ISAscale IVT-MODULAR broadcasts on CAN1 (the vehicle bus the PCU is on):
7+
* - 0x521: Current
8+
* - 0x522: Voltage 1
9+
* - 0x523: Voltage 2 (pack voltage on SN5)
10+
* - 0x524: Voltage 3
11+
* - 0x525: Temperature
12+
*
13+
* Frame IDs and byte layout live in the shared CAN library
14+
* (common/FEB_CAN_Library_SN4); decoding uses the generated FEB_CAN_IVT_*_FRAME_ID
15+
* macros and feb_can_ivt_*_unpack() functions, like every other PCU CAN package.
16+
*
17+
* The PCU uses the IVT's measured pack voltage and current for RMS torque/power
18+
* limiting (see FEB_RMS.c).
19+
*/
20+
21+
#ifndef INC_FEB_CAN_IVT_H_
22+
#define INC_FEB_CAN_IVT_H_
23+
24+
#include <stdint.h>
25+
#include <stdbool.h>
26+
27+
/* IVT voltage channel wired to the pack terminals (1, 2, or 3). SN5 uses 2. */
28+
#define FEB_CAN_IVT_PACK_VOLTAGE_CHANNEL 2
29+
30+
/* Data is considered stale after this many ms without an IVT frame. */
31+
#define FEB_CAN_IVT_DATA_TIMEOUT_MS 1000
32+
33+
/**
34+
* @brief Register IVT CAN RX callbacks on CAN1
35+
*/
36+
void FEB_CAN_IVT_Init(void);
37+
38+
/**
39+
* @brief Get pack voltage from the IVT (channel FEB_CAN_IVT_PACK_VOLTAGE_CHANNEL)
40+
* @return Pack voltage in volts; 0.0 if data is stale or never received
41+
*/
42+
float FEB_CAN_IVT_GetVoltage(void);
43+
44+
/**
45+
* @brief Get pack current from the IVT
46+
* @return Pack current in amps (may be stale — gate with FEB_CAN_IVT_IsDataFresh)
47+
*/
48+
float FEB_CAN_IVT_GetCurrent(void);
49+
50+
/**
51+
* @brief Check whether IVT data is fresh
52+
* @param timeout_ms Maximum acceptable age in milliseconds
53+
* @return true if a frame arrived within timeout_ms, false otherwise
54+
*/
55+
bool FEB_CAN_IVT_IsDataFresh(uint32_t timeout_ms);
56+
57+
#endif /* INC_FEB_CAN_IVT_H_ */

PCU/Core/User/Inc/FEB_Main.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@
1717
#include "FEB_RMS.h"
1818
#include "FEB_CAN_RMS.h"
1919
#include "FEB_CAN_BMS.h"
20+
#include "FEB_CAN_IVT.h"
2021
#include "FEB_CAN_Diagnostics.h"
2122
#include "FEB_CAN_TPS.h"
2223
#include "FEB_PCU_Commands.h"

PCU/Core/User/Inc/FEB_RMS_Config.h

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -57,6 +57,20 @@ extern "C"
5757
*/
5858
#define LOW_PACK_VOLTAGE 4200 /* 420.0V in decivolts */
5959

60+
/**
61+
* @brief Low pack voltage threshold in volts (for IVT-measured comparisons)
62+
* @note Same threshold as LOW_PACK_VOLTAGE, expressed in volts since the IVT
63+
* getters return volts. Below this, torque is limited to MAX_TORQUE_LOW_V.
64+
*/
65+
#define LOW_PACK_VOLTAGE_V 420.0f
66+
67+
/**
68+
* @brief IVT-measured pack current limit in Amps for the protective torque derate
69+
* @note Backstop to the power cap: above this measured current, torque is scaled
70+
* down proportionally. Bench-tune to avoid oscillation before trusting.
71+
*/
72+
#define IVT_CURRENT_LIMIT_A PEAK_CURRENT
73+
6074
/**
6175
* @brief Initial/nominal pack voltage in decivolts
6276
* @note Used for startup calculations

PCU/Core/User/Src/FEB_CAN_BMS.c

Lines changed: 10 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -96,7 +96,6 @@ static void FEB_CAN_BMS_Callback(FEB_CAN_Instance_t instance, uint32_t can_id, F
9696
{
9797
(void)instance;
9898
(void)id_type;
99-
(void)length;
10099
(void)user_data;
101100

102101
/* NOTE: This callback runs in ISR context - avoid logging and blocking operations */
@@ -105,8 +104,11 @@ static void FEB_CAN_BMS_Callback(FEB_CAN_Instance_t instance, uint32_t can_id, F
105104

106105
if (can_id == FEB_CAN_BMS_ACCUMULATOR_TEMPERATURE_FRAME_ID)
107106
{
108-
BMS_MESSAGE.temperature = data[2] << 8 | data[3];
109-
BMS_MESSAGE.max_temperature = (float)BMS_MESSAGE.temperature / 10.0f;
107+
/* deci-degrees C, max cell temperature (signed) */
108+
struct feb_can_bms_accumulator_temperature_t t;
109+
feb_can_bms_accumulator_temperature_unpack(&t, data, length);
110+
BMS_MESSAGE.temperature = (uint16_t)t.max_cell_temperature;
111+
BMS_MESSAGE.max_temperature = (float)t.max_cell_temperature / 10.0f;
110112
}
111113
else if (can_id == FEB_CAN_BMS_STATE_FRAME_ID)
112114
{
@@ -121,8 +123,11 @@ static void FEB_CAN_BMS_Callback(FEB_CAN_Instance_t instance, uint32_t can_id, F
121123
}
122124
else if (can_id == FEB_CAN_BMS_ACCUMULATOR_VOLTAGE_FRAME_ID)
123125
{
124-
BMS_MESSAGE.voltage = (data[0] << 8) | (data[1]);
125-
BMS_MESSAGE.accumulator_voltage = (float)BMS_MESSAGE.voltage / 10.0f;
126+
/* decivolts, cell-sum pack voltage — used for the `bms` console display */
127+
struct feb_can_bms_accumulator_voltage_t v;
128+
feb_can_bms_accumulator_voltage_unpack(&v, data, length);
129+
BMS_MESSAGE.voltage = v.total_pack_voltage;
130+
BMS_MESSAGE.accumulator_voltage = (float)v.total_pack_voltage / 10.0f;
126131
}
127132
}
128133

PCU/Core/User/Src/FEB_CAN_IVT.c

Lines changed: 152 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,152 @@
1+
/**
2+
* @file FEB_CAN_IVT.c
3+
* @brief IVT (Isabellenhutte) Current/Voltage sensor CAN reception for the PCU
4+
* @author Formula Electric @ Berkeley
5+
*
6+
* Decodes the IVT-S broadcast on CAN1 using the generated feb_can_ivt_*_unpack()
7+
* functions, exactly like the other PCU CAN packages. The measured pack voltage
8+
* and current feed the RMS torque/power limiter (FEB_RMS.c).
9+
*/
10+
11+
#include "FEB_CAN_IVT.h"
12+
#include "feb_can_lib.h"
13+
#include "feb_can.h"
14+
#include "stm32f4xx_hal.h"
15+
#include "cmsis_compiler.h"
16+
#include <stddef.h>
17+
18+
/* Note: float reads are atomic on ARM Cortex-M4, so no critical section needed. */
19+
20+
typedef struct
21+
{
22+
volatile float current_mA;
23+
volatile float voltage_1_mV;
24+
volatile float voltage_2_mV;
25+
volatile float voltage_3_mV;
26+
volatile uint32_t last_rx_tick; /* 0 = never received */
27+
} IVT_Data_t;
28+
29+
static IVT_Data_t ivt_data = {0};
30+
31+
/* ISR-context callback — no logging or blocking work. */
32+
static void FEB_CAN_IVT_Callback(FEB_CAN_Instance_t instance, uint32_t can_id, FEB_CAN_ID_Type_t id_type,
33+
const uint8_t *data, uint8_t length, void *user_data)
34+
{
35+
(void)instance;
36+
(void)id_type;
37+
(void)user_data;
38+
39+
switch (can_id)
40+
{
41+
case FEB_CAN_IVT_CURRENT_FRAME_ID:
42+
{
43+
struct feb_can_ivt_current_t msg;
44+
if (feb_can_ivt_current_unpack(&msg, data, length) < 0)
45+
{
46+
return;
47+
}
48+
/* Current in mA, negate for reversed direction (matches BMS convention) */
49+
ivt_data.current_mA = (float)msg.current * (-0.001f) * 1000.0f;
50+
__DMB();
51+
ivt_data.last_rx_tick = HAL_GetTick();
52+
break;
53+
}
54+
55+
case FEB_CAN_IVT_VOLTAGE1_FRAME_ID:
56+
{
57+
struct feb_can_ivt_voltage1_t msg;
58+
if (feb_can_ivt_voltage1_unpack(&msg, data, length) < 0)
59+
{
60+
return;
61+
}
62+
ivt_data.voltage_1_mV = (float)msg.voltage1;
63+
__DMB();
64+
ivt_data.last_rx_tick = HAL_GetTick();
65+
break;
66+
}
67+
68+
case FEB_CAN_IVT_VOLTAGE2_FRAME_ID:
69+
{
70+
struct feb_can_ivt_voltage2_t msg;
71+
if (feb_can_ivt_voltage2_unpack(&msg, data, length) < 0)
72+
{
73+
return;
74+
}
75+
ivt_data.voltage_2_mV = (float)msg.voltage2;
76+
__DMB();
77+
ivt_data.last_rx_tick = HAL_GetTick();
78+
break;
79+
}
80+
81+
case FEB_CAN_IVT_VOLTAGE3_FRAME_ID:
82+
{
83+
struct feb_can_ivt_voltage3_t msg;
84+
if (feb_can_ivt_voltage3_unpack(&msg, data, length) < 0)
85+
{
86+
return;
87+
}
88+
ivt_data.voltage_3_mV = (float)msg.voltage3;
89+
__DMB();
90+
ivt_data.last_rx_tick = HAL_GetTick();
91+
break;
92+
}
93+
94+
default:
95+
break;
96+
}
97+
}
98+
99+
void FEB_CAN_IVT_Init(void)
100+
{
101+
ivt_data.current_mA = 0.0f;
102+
ivt_data.voltage_1_mV = 0.0f;
103+
ivt_data.voltage_2_mV = 0.0f;
104+
ivt_data.voltage_3_mV = 0.0f;
105+
ivt_data.last_rx_tick = 0;
106+
107+
/* Single MASK registration covering 0x520-0x527 (IVT frames are 0x521-0x525).
108+
* One hardware filter bank; the callback ignores anything else in range. */
109+
FEB_CAN_RX_Params_t rx_params = {
110+
.instance = FEB_CAN_INSTANCE_1,
111+
.can_id = 0x520,
112+
.id_type = FEB_CAN_ID_STD,
113+
.filter_type = FEB_CAN_FILTER_MASK,
114+
.mask = 0x7F8,
115+
.fifo = FEB_CAN_FIFO_0,
116+
.callback = FEB_CAN_IVT_Callback,
117+
.user_data = NULL,
118+
};
119+
FEB_CAN_RX_Register(&rx_params);
120+
}
121+
122+
float FEB_CAN_IVT_GetVoltage(void)
123+
{
124+
if (!FEB_CAN_IVT_IsDataFresh(FEB_CAN_IVT_DATA_TIMEOUT_MS))
125+
{
126+
return 0.0f;
127+
}
128+
129+
#if FEB_CAN_IVT_PACK_VOLTAGE_CHANNEL == 1
130+
return ivt_data.voltage_1_mV * 0.001f;
131+
#elif FEB_CAN_IVT_PACK_VOLTAGE_CHANNEL == 2
132+
return ivt_data.voltage_2_mV * 0.001f;
133+
#elif FEB_CAN_IVT_PACK_VOLTAGE_CHANNEL == 3
134+
return ivt_data.voltage_3_mV * 0.001f;
135+
#else
136+
#error "FEB_CAN_IVT_PACK_VOLTAGE_CHANNEL must be 1, 2, or 3"
137+
#endif
138+
}
139+
140+
float FEB_CAN_IVT_GetCurrent(void)
141+
{
142+
return ivt_data.current_mA * 0.001f;
143+
}
144+
145+
bool FEB_CAN_IVT_IsDataFresh(uint32_t timeout_ms)
146+
{
147+
if (ivt_data.last_rx_tick == 0)
148+
{
149+
return false;
150+
}
151+
return (HAL_GetTick() - ivt_data.last_rx_tick) < timeout_ms;
152+
}

PCU/Core/User/Src/FEB_Main.c

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -120,6 +120,11 @@ void FEB_Main_Setup(void)
120120
// === CHECKPOINT 6: BMS ready ===
121121
LOG_I(TAG_MAIN, "[6/8] BMS initialized");
122122
HAL_Delay(50);
123+
124+
// IVT current/voltage sensor RX (pack voltage + current for RMS limiting)
125+
FEB_CAN_IVT_Init();
126+
LOG_I(TAG_MAIN, "[6/8] IVT initialized");
127+
HAL_Delay(50);
123128
}
124129
else
125130
{

0 commit comments

Comments
 (0)