Projects/Airspeed PCB
2026In Progress

Airspeed PCB

Custom PCB system for capturing real-time air pressure data across aerodynamic components on a Formula SAE race car.

AltiumC++React.js
Airspeed PCB cover

The Board

A standalone data acquisition board designed for aerodynamic testing on a Formula SAE race car. During testing, 12 Honeywell differential pressure sensors connect to pitot tubes mounted on an aero-rig. The sensors measure differential pressure across aerodynamic surfaces, providing the aero team with real-world data to validate their CFD models. Data is logged through the ESP32's flash memory and stored long-term as .csv files on an SD card for post-session analysis.
ComponentRole
ESP32-S3-DevKitC-1Main microcontroller with built-in WiFi and USB CDC
Honeywell ABP2 Sensors (×12)Differential pressure measurement via pitot tubes
TCA9548A I2C Mux (×2)Route 12 sensors on a shared I2C bus
PCF8523 RTCReal-time clock for timestamping logged data
CAN TransceiverVehicle CAN bus integration

The Code

FILES
main.cpp
1#include <Arduino.h>
2#include <Wire.h>
3#include <virtualTimer.h>
4#include "TCA9548A.h"
5#include "airSpeed.h"
6#include "PCF8523.h"
7
8// GPIO Definitions
9#define SDA_PIN 21
10#define SCL_PIN 22
11
12// SD card pins
13#define SD_MISO 27
14#define SD_CLK 14
15#define SD_MOSI 13
16#define SD_CS 15
17
18// Hardware stuff
19TCA9548A muxA(0x70); // MUX A at address 0x70
20TCA9548A muxB(0x71); // MUX B at address 0x71
21PCF8523_RTC rtc;
22
23// Airspeed sensors
24// MUX A channels 0-7, MUX B channels 0-3
25static constexpr uint8_t MUX_A_COUNT = 6;
26static constexpr uint8_t MUX_B_COUNT = 6;
27static constexpr uint8_t SENSOR_COUNT = MUX_A_COUNT + MUX_B_COUNT;
28
29AirSpeedSensor sensorsA[] = {
30 AirSpeedSensor(muxA, 0), AirSpeedSensor(muxA, 1),
31 AirSpeedSensor(muxA, 2), AirSpeedSensor(muxA, 3),
32 AirSpeedSensor(muxA, 4), AirSpeedSensor(muxA, 5),
33};
34
35AirSpeedSensor sensorsB[] = {
36 AirSpeedSensor(muxB, 0), AirSpeedSensor(muxB, 1),
37 AirSpeedSensor(muxB, 2), AirSpeedSensor(muxB, 3),
38 AirSpeedSensor(muxB, 4), AirSpeedSensor(muxB, 5),
39};
40
41// Readings storage
42ABP2Reading readings[SENSOR_COUNT];
43RTCTime currentTime;
44
45// Timer group
46VirtualTimerGroup timerGroup;
47
48// Forward declarations
49void pollSensors();
50void printData();
51
52// Callbacks
53void pollSensors() {
54 // Read RTC
55 currentTime = rtc.readTime();
56
57 // Read all MUX A sensors
58 for (uint8_t i = 0; i < MUX_A_COUNT; i++) {
59 readings[i] = sensorsA[i].read();
60 }
61
62 // Read all MUX B sensors
63 for (uint8_t i = 0; i < MUX_B_COUNT; i++) {
64 readings[MUX_A_COUNT + i] = sensorsB[i].read();
65 }
66}
67
68void printData() {
69 // Print RTC timestamp
70 if (currentTime.valid) {
71 char ts[24];
72 snprintf(ts, sizeof(ts), "20%02u-%02u-%02u %02u:%02u:%02u",
73 currentTime.year, currentTime.month, currentTime.day,
74 currentTime.hours, currentTime.minutes, currentTime.seconds);
75 Serial.print("[");
76 Serial.print(ts);
77 Serial.print("] ");
78 if (currentTime.os_flag) Serial.print("(RTC CLOCK LOST) ");
79 } else {
80 Serial.print("[RTC ERROR] ");
81 }
82
83 Serial.print("t=");
84 Serial.print(millis());
85 Serial.println("ms");
86
87 // Print each sensor reading
88 for (uint8_t i = 0; i < SENSOR_COUNT; i++) {
89 const char *muxLabel = (i < MUX_A_COUNT) ? "A" : "B";
90 uint8_t ch = (i < MUX_A_COUNT) ? i : (i - MUX_A_COUNT);
91
92 Serial.print(" MUX");
93 Serial.print(muxLabel);
94 Serial.print(":CH");
95 Serial.print(ch);
96
97 if (readings[i].valid) {
98 Serial.print(" P=");
99 Serial.print(readings[i].pressure_inH2O, 4);
100 Serial.print(" inH2O T=");
101 Serial.print(readings[i].temperature_C, 1);
102 Serial.println(" C");
103 } else {
104 Serial.print(" INVALID (status=0x"); // error catching
105 Serial.print(readings[i].status, HEX);
106 Serial.println(")");
107 }
108 }
109 Serial.println("---");
110}
111
112void setup() {
113 Serial.begin(9600);
114 Wire.begin(SDA_PIN, SCL_PIN);
115
116 Serial.println("DAQ Airspeed — Initializing...");
117
118 // Init MUXes
119 if (!muxA.begin()) Serial.println("ERROR: MUX A (0x70) not found!");
120 if (!muxB.begin()) Serial.println("ERROR: MUX B (0x71) not found!");
121
122 // Init RTC
123 if (!rtc.begin()) {
124 Serial.println("ERROR: PCF8523 RTC not found!");
125 } else {
126 Serial.println("PCF8523 RTC OK");
127 }
128
129 // Init sensors — report which ones respond
130 Serial.print("Sensors on MUX A: ");
131 for (uint8_t i = 0; i < MUX_A_COUNT; i++) {
132 bool ok = sensorsA[i].begin();
133 Serial.print(ok ? "+" : "-");
134 }
135 Serial.println();
136
137 Serial.print("Sensors on MUX B: ");
138 for (uint8_t i = 0; i < MUX_B_COUNT; i++) {
139 bool ok = sensorsB[i].begin();
140 Serial.print(ok ? "+" : "-");
141 }
142 Serial.println();
143
144 // CSV-style header for serial
145 Serial.println();
146 Serial.println("timestamp, millis, mux, ch, inH2O, temp_C");
147 Serial.println("========================================================");
148
149 // Schedule repeating timers
150 timerGroup.AddTimer(1000, pollSensors); // poll sensors every 1s
151 timerGroup.AddTimer(1000, printData); // print to serial every 1s
152
153 Serial.println("Timers started.");
154}
155
156void loop() {
157 timerGroup.Tick(millis());
158}