Problem Statement
In student-built electric and fuel-efficient vehicles, reliable real-time telemetry is essential for safe operation, performance tuning, and regulatory compliance. However, many teams rely on ad-hoc wiring, inconsistent sensor calibration, or unstable communication protocols, resulting in delayed diagnostics, inaccurate data, and system faults during competition.
A major challenge is obtaining accurate brake-pressure measurements and distributing them over a fault-tolerant CAN Bus network. The brake-pressure transducer outputs a low-voltage analog signal that is highly sensitive to noise, grounding issues, and sampling errors. Without a robust ADC pipeline, proper calibration, and consistent CAN communication, the vehicle cannot reliably measure braking force, undermining data logging, safety systems, and driver feedback.
Therefore, there is a clear need for a stable CAN Bus implementation in Normal Mode and a precisely calibrated ADC acquisition system to ensure dependable, high-frequency brake-pressure telemetry across the vehicle. A reliable solution must deliver accurate PSI readings, handle electrical noise, maintain correct frame timing, and integrate seamlessly into the team’s existing CAN architecture.
Solution
When implementing the CAN normal mode configuration, I initially followed documents on the STM32 website to understand the communication protocol and how to implement it myself. After ensuring that it works by debugging with SWD and logic analyzer on pulseview, I used the Bruin Supermileage's library which initializes and abstracts the HAL libraries implementation of CAN, which is where "#include "smv_canbus.h" comes from.
For my design of the brake transducer wrapper. I have a pseudo constructor which creates an instance of the brake trans with default configurations to the ADC in STM32. The wrapper I created is based off of the HAL ADC drivers which connects the software to the peripherals. When the analog voltage is received through the ADC, it automatically converts to a PSI value and sends the information via CAN.
Below is implementation of CAN normal mode and brake transducer in C (entire code is not shown here):
#include "main.h"
#include "smv_canbus.h" #include "smv_braketrans.h"
#include <string.h>
CAN_HandleTypeDef hcan1;
CANBUS can1;
double send_num = 0;
ADC_HandleTypeDef hadc1;
BrakeTransTypeDef bt1;
int main(void)
{
HAL_Init();
SystemClock_Config();
MX_GPIO_Init();
can1 = CAN_new(); // construct a CAN object
can1.init(&can1, HS2, &hcan1); // methods require pointers to self because C has no "this" pointer
can1.addFilterDeviceData(&can1, UI, Horn);
can1.begin(&can1);
bt1 = BRAKE_new();
bt1.init(&bt1, HS1, &hadc1);
bt1.begin(&bt1);
while (1)
{
bt1.collect(&bt1);
can1.send(&can1, bt1.psi_value, bt1.data_type); // Sends data through can using psi values read from adc
send_num+=0.01;
/*NOTE:
* LED toggle with callback is disabled to accommodate higher frequency
*/
HAL_Delay(10);
}
}
/* USER CODE BEGIN 4 */
void HAL_CAN_RxFifo0MsgPendingCallback(CAN_HandleTypeDef *CanHandle)
{
/* Get RX message from FIFO0 and fill the data on the related FIFO0 user declared header
(RxHeaderFIFO0) and table (RxDataFIFO0) */
if (HAL_CAN_GetRxMessage(CanHandle, CAN_RX_FIFO0, &(can1.RxHeaderFIFO0), can1.RxDataFIFO0) != HAL_OK)
{
/* Reception Error */
Error_Handler();
}else{
//HAL_GPIO_TogglePin(GPIOA, GPIO_PIN_5);
CAN_Interrupt_Helper(&can1);
}
}
#endif /* USE_FULL_ASSERT *Brake Transducer Header
Below is the struct to initialize ADC for STM32 while creating an instance for the brake transducer. We use a voltage divider since output of brake transducer is 4.5V but ADC input for STM32 only accepts 3.3V max. The implementation of the brake transducer is with the assumption that the other teams within the Bruin Supermileage need a quick implementation. Instances of the brake trans can be set manually if needed.
#ifndef _SMV_BRAKETRANS_H
#define _SMV_BRAKETRANS_H
#include "stm32f4xx_hal_adc.h"
#include <stdint.h>
typedef struct
{
// stm32 adc setup
ADC_HandleTypeDef *hadc;
ADC_ChannelConfTypeDef sConfig;
int device_id;
uint8_t data_type; // Pressure datatype (6) for HS Message
uint16_t adc_raw; // last ADC reading (0-4095)
double psi_value; // calculated brake pressure [psi]
uint8_t fault_flag; // 0 = OK, 1 = sensor fault, etc.
void (*init)(BrakeTransTypeDef*, int, ADC_HandleTypeDef*);
void (*begin)(BrakeTransTypeDef*);
void (*collect)(BrakeTransTypeDef*);
double (*getPsi)(BrakeTransTypeDef);
uint16_t (*getAdc)(BrakeTransTypeDef);
} BrakeTransTypeDef;
// ---- Hardware Constants ----
#define BRAKE_TRANS_ADC_MAX 4095U
#define BRAKE_TRANS_VREF 3.3 // STM32 ADC reference voltage
#define BRAKE_TRANS_DIVIDER 2.0 // Voltage divider ratio (sensor -> ADC)
#define BRAKE_TRANS_VMAX 4.5 // Sensor full-scale voltage
#define BRAKE_TRANS_VMIN 0.5 // Sensor zero-pressure voltage
// ---- Calibration Constants ----
#define BRAKE_TRANS_SLOPE 125.0
#define BRAKE_TRANS_OFFSET 62.5
// ---- Physical Limits ----
#define PSI_MAX 500.0
#define PSI_MIN 0.0
// Constructor for Brake Trans which also includes ADC setup
BrakeTransTypeDef BRAKE_new(void);
#endif // _SMV_BRAKETRANS_H