Skip to content

Commit 71912d5

Browse files
authored
Create Haltech_To_Arduino_DRAFT.ino
1 parent 3c46279 commit 71912d5

1 file changed

Lines changed: 303 additions & 0 deletions

File tree

Haltech_To_Arduino_DRAFT.ino

Lines changed: 303 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,303 @@
1+
// =======================================================
2+
// Travis Digital Dash – Haltech CAN → TS Dash Serial Bridge
3+
// Arduino Mega 2560 + MCP2515 (16MHz) @ 500kbps
4+
//
5+
// Serial side emulates the INI requirements:
6+
// queryCommand = "Q" -> returns "speeduino-travis"
7+
// versionInfo = "S" -> returns VERSION string
8+
// ochGetCommand = "r" -> returns 87-byte output block
9+
//
10+
// INI OutputChannels (selected):
11+
// rpm U16 @ 0
12+
// oilanalograw U08 @ 3 (°C + 40)
13+
// mapraw U16 @ 4 (kPa absolute)
14+
// coolantanalograw U08 @ 7 (°C + 40)
15+
// oilPressure U08 @ 10 (PSI)
16+
// fuelPressure U08 @ 11 (PSI)
17+
// fuellevel U08 @ 15 (%)
18+
// vss U16 @ 19 (kph)
19+
// leftTurn U08 @ 40 (0/1)
20+
// rightTurn U08 @ 41 (0/1)
21+
// cel U08 @ 42 (0/1)
22+
// highBeam U08 @ 43 (0/1)
23+
// handbrake U08 @ 44 (0/1)
24+
// =======================================================
25+
26+
#include <Arduino.h>
27+
#include <SPI.h>
28+
#include <mcp_can.h>
29+
30+
// ===================== IDENTITY (INI MATCH) =====================
31+
static const char SIGNATURE[] = "speeduino-travis";
32+
static const char VERSION[] = "Travis Digital Dash v1.0";
33+
34+
// ===================== SERIAL =====================
35+
static constexpr uint32_t BAUD_RATE = 115200;
36+
37+
// ===================== CAN (MCP2515) =====================
38+
#define CAN_CS_PIN 53
39+
#define CAN_INT_PIN 2
40+
#define CAN_CLOCK MCP_16MHZ
41+
#define CAN_SPEED CAN_500KBPS
42+
43+
MCP_CAN CAN(CAN_CS_PIN);
44+
45+
// =======================================================
46+
// HALTECH CAN MAPPING (DEFAULTS / YOU MAY NEED TO ADJUST)
47+
// -------------------------------------------------------
48+
// Haltech CAN layouts can vary by ECU config and stream.
49+
// These defaults are *common* patterns, but if any channel
50+
// shows wrong, sniff the bus and update IDs/bytes/scales.
51+
// =======================================================
52+
53+
// --------- Frame IDs (11-bit) ----------
54+
static constexpr uint16_t ID_RPM_MAP = 0x360; // rpm + map
55+
static constexpr uint16_t ID_TEMPS = 0x361; // clt + oil temp (example)
56+
static constexpr uint16_t ID_PRESSURES = 0x362; // oil psi + fuel psi (example)
57+
static constexpr uint16_t ID_SPEED_FUEL = 0x363; // speed kph + fuel % (example)
58+
static constexpr uint16_t ID_INDICATORS = 0x364; // turn/cel/high/handbrake (example)
59+
60+
// --------- Byte layouts / scaling ----------
61+
// RPM: U16 little-endian, units rpm
62+
// MAP: U16 little-endian, units kPa absolute
63+
// CLT/OIL: int16 or u16? (varies). We convert to °C then encode as U08 (°C + 40).
64+
// Pressures: usually kPa or PSI; we output PSI already scaled in firmware (U08).
65+
// Speed: U16 kph
66+
// Fuel: U08 percent
67+
//
68+
// If your Haltech stream uses different scaling (e.g. 0.1 kPa, 0.1°C), adjust here:
69+
static constexpr float TEMP_SCALE = 1.0f; // e.g. 0.1f if temp is in 0.1°C
70+
static constexpr float MAP_SCALE = 1.0f; // e.g. 0.1f if map is in 0.1 kPa
71+
static constexpr float RPM_SCALE = 1.0f; // e.g. 0.5f if half-RPM, etc.
72+
static constexpr float PSI_SCALE = 1.0f; // e.g. 0.145038 if kPa->psi, etc.
73+
74+
// ===================== OUTPUT BLOCK =====================
75+
static constexpr uint8_t OCH_BLOCK_SIZE = 87; // ini: ochBlockSize = 87
76+
static uint8_t och[OCH_BLOCK_SIZE];
77+
78+
// ===================== LIVE VALUES (decoded from CAN) =====================
79+
static volatile uint16_t g_rpm = 0;
80+
static volatile uint16_t g_map_kpa = 100; // default ~atmosphere
81+
static volatile int16_t g_clt_c = 20;
82+
static volatile int16_t g_oil_c = 20;
83+
static volatile uint8_t g_oil_psi = 0;
84+
static volatile uint8_t g_fuel_psi = 0;
85+
static volatile uint16_t g_speed_kph = 0;
86+
static volatile uint8_t g_fuel_pct = 0;
87+
88+
static volatile uint8_t g_leftTurn = 0;
89+
static volatile uint8_t g_rightTurn = 0;
90+
static volatile uint8_t g_cel = 0;
91+
static volatile uint8_t g_highBeam = 0;
92+
static volatile uint8_t g_handbrake = 0;
93+
94+
// Optional: basic timeout protection
95+
static uint32_t lastCanRxMs = 0;
96+
97+
// ===================== SMALL HELPERS =====================
98+
static inline uint16_t u16le(const uint8_t *b) {
99+
return (uint16_t)b[0] | ((uint16_t)b[1] << 8);
100+
}
101+
static inline int16_t s16le(const uint8_t *b) {
102+
return (int16_t)((uint16_t)b[0] | ((uint16_t)b[1] << 8));
103+
}
104+
105+
static inline uint8_t clampU8(int v) {
106+
if (v < 0) return 0;
107+
if (v > 255) return 255;
108+
return (uint8_t)v;
109+
}
110+
111+
static inline uint8_t tempC_to_iniRaw(int16_t tempC) {
112+
// INI expects U08 = (°C + 40)
113+
return clampU8((int)tempC + 40);
114+
}
115+
116+
static void writeU16LE(uint8_t *dst, uint16_t v) {
117+
dst[0] = (uint8_t)(v & 0xFF);
118+
dst[1] = (uint8_t)(v >> 8);
119+
}
120+
121+
// ===================== CAN RECEIVE =====================
122+
static void handleCanFrame(uint16_t id, const uint8_t *buf, uint8_t len) {
123+
(void)len;
124+
lastCanRxMs = millis();
125+
126+
switch (id) {
127+
case ID_RPM_MAP: {
128+
// [0..1]=RPM, [2..3]=MAP (kPa abs)
129+
uint16_t rpmRaw = u16le(&buf[0]);
130+
uint16_t mapRaw = u16le(&buf[2]);
131+
132+
uint32_t rpm = (uint32_t)(rpmRaw * RPM_SCALE);
133+
uint32_t map = (uint32_t)(mapRaw * MAP_SCALE);
134+
135+
if (rpm > 30000) rpm = 30000;
136+
if (map > 4000) map = 4000;
137+
138+
g_rpm = (uint16_t)rpm;
139+
g_map_kpa = (uint16_t)map;
140+
} break;
141+
142+
case ID_TEMPS: {
143+
// Example: [0..1]=CLT, [2..3]=OilTemp (in °C * TEMP_SCALE)
144+
int16_t cltRaw = s16le(&buf[0]);
145+
int16_t oilRaw = s16le(&buf[2]);
146+
147+
int16_t cltC = (int16_t)(cltRaw * TEMP_SCALE);
148+
int16_t oilC = (int16_t)(oilRaw * TEMP_SCALE);
149+
150+
// sanity
151+
if (cltC < -40) cltC = -40;
152+
if (cltC > 215) cltC = 215;
153+
if (oilC < -40) oilC = -40;
154+
if (oilC > 215) oilC = 215;
155+
156+
g_clt_c = cltC;
157+
g_oil_c = oilC;
158+
} break;
159+
160+
case ID_PRESSURES: {
161+
// Example: [0]=oil psi, [1]=fuel psi (already PSI)
162+
// If yours is kPa, change PSI_SCALE and conversion.
163+
uint16_t oilRaw = buf[0];
164+
uint16_t fuelRaw = buf[1];
165+
166+
uint16_t oilPsi = (uint16_t)(oilRaw * PSI_SCALE);
167+
uint16_t fuelPsi = (uint16_t)(fuelRaw * PSI_SCALE);
168+
169+
if (oilPsi > 255) oilPsi = 255;
170+
if (fuelPsi > 255) fuelPsi = 255;
171+
172+
g_oil_psi = (uint8_t)oilPsi;
173+
g_fuel_psi = (uint8_t)fuelPsi;
174+
} break;
175+
176+
case ID_SPEED_FUEL: {
177+
// Example: [0..1]=speed kph, [2]=fuel %
178+
g_speed_kph = u16le(&buf[0]);
179+
g_fuel_pct = buf[2];
180+
if (g_fuel_pct > 100) g_fuel_pct = 100;
181+
} break;
182+
183+
case ID_INDICATORS: {
184+
// Example: packed bits in buf[0]
185+
// bit0=left, bit1=right, bit2=cel, bit3=high, bit4=handbrake
186+
uint8_t bits = buf[0];
187+
g_leftTurn = (bits & (1 << 0)) ? 1 : 0;
188+
g_rightTurn = (bits & (1 << 1)) ? 1 : 0;
189+
g_cel = (bits & (1 << 2)) ? 1 : 0;
190+
g_highBeam = (bits & (1 << 3)) ? 1 : 0;
191+
g_handbrake = (bits & (1 << 4)) ? 1 : 0;
192+
} break;
193+
194+
default:
195+
break;
196+
}
197+
}
198+
199+
// ===================== BUILD THE 87-BYTE OCH BLOCK =====================
200+
static void buildOchBlock() {
201+
// Clear everything (anything not defined in your INI stays 0)
202+
memset(och, 0, sizeof(och));
203+
204+
// Match INI offsets exactly :contentReference[oaicite:4]{index=4}
205+
// rpm: U16 @ 0
206+
writeU16LE(&och[0], g_rpm);
207+
208+
// oilanalograw: U08 @ 3 (°C + 40)
209+
och[3] = tempC_to_iniRaw(g_oil_c);
210+
211+
// mapraw: U16 @ 4 (kPa abs)
212+
writeU16LE(&och[4], g_map_kpa);
213+
214+
// coolantanalograw: U08 @ 7 (°C + 40)
215+
och[7] = tempC_to_iniRaw(g_clt_c);
216+
217+
// oilPressure: U08 @ 10 (PSI)
218+
och[10] = g_oil_psi;
219+
220+
// fuelPressure: U08 @ 11 (PSI)
221+
och[11] = g_fuel_psi;
222+
223+
// fuellevel: U08 @ 15 (%)
224+
och[15] = g_fuel_pct;
225+
226+
// vss: U16 @ 19 (kph)
227+
writeU16LE(&och[19], g_speed_kph);
228+
229+
// indicators: U08 @ 40..44
230+
och[40] = g_leftTurn;
231+
och[41] = g_rightTurn;
232+
och[42] = g_cel;
233+
och[43] = g_highBeam;
234+
och[44] = g_handbrake;
235+
}
236+
237+
// ===================== TS SERIAL COMMAND HANDLING =====================
238+
// TS will send:
239+
// 'Q' -> expects signature string :contentReference[oaicite:5]{index=5}
240+
// 'S' -> expects version string :contentReference[oaicite:6]{index=6}
241+
// 'r' -> expects ochBlockSize bytes :contentReference[oaicite:7]{index=7}
242+
//
243+
// Important: Do NOT print debug text while TS is connected.
244+
static void handleSerialByte(uint8_t c) {
245+
if (c == 'Q') {
246+
Serial.print(SIGNATURE);
247+
return;
248+
}
249+
if (c == 'S') {
250+
Serial.print(VERSION);
251+
return;
252+
}
253+
if (c == 'r') {
254+
buildOchBlock();
255+
Serial.write(och, OCH_BLOCK_SIZE);
256+
return;
257+
}
258+
// ignore anything else (keeps TS happy)
259+
}
260+
261+
// ===================== SETUP =====================
262+
void setup() {
263+
Serial.begin(BAUD_RATE);
264+
265+
pinMode(CAN_INT_PIN, INPUT);
266+
267+
if (CAN.begin(MCP_ANY, CAN_SPEED, CAN_CLOCK) != CAN_OK) {
268+
// No Serial prints here if you want TS to connect cleanly later.
269+
// If you need debug, temporarily uncomment this:
270+
// Serial.println("CAN INIT FAILED");
271+
while (1) {;}
272+
}
273+
274+
CAN.setMode(MCP_NORMAL);
275+
lastCanRxMs = millis();
276+
}
277+
278+
// ===================== LOOP =====================
279+
void loop() {
280+
// --- CAN service ---
281+
if (!digitalRead(CAN_INT_PIN)) {
282+
long unsigned int rxId32;
283+
unsigned char len = 0;
284+
unsigned char buf[8];
285+
286+
if (CAN.readMsgBuf(&rxId32, &len, buf) == CAN_OK) {
287+
uint16_t id = (uint16_t)(rxId32 & 0x7FF); // 11-bit
288+
handleCanFrame(id, buf, len);
289+
}
290+
}
291+
292+
// --- Optional: CAN timeout fallback (prevent stale numbers) ---
293+
if (millis() - lastCanRxMs > 1000) {
294+
g_rpm = 0;
295+
g_speed_kph = 0;
296+
// keep temps/map last-known; you can zero them too if you prefer
297+
}
298+
299+
// --- Serial service ---
300+
while (Serial.available()) {
301+
handleSerialByte((uint8_t)Serial.read());
302+
}
303+
}

0 commit comments

Comments
 (0)