Files
VolvoRTI/Arduino/LIN_to_IR/LIN_to_IR.cpp
T

205 lines
4.8 KiB
C++

#include <Arduino.h>
#include <SoftwareSerial.h>
#include "lin_frame.h"
#define IR_ARDUINO_PIN 10
#define LED_PIN 3
#define RX_PIN 8
#define TX_PIN 2 // Unused dummy pin for SoftwareSerial TX
#define FAULT_PIN 9
#define CS_PIN 11
#define SYN_FIELD 0x55
#define SWM_ID 0x20
SoftwareSerial LINBusSerial(RX_PIN, TX_PIN);
// RC-6 timing constants
// 1 time unit (1t) = 444us
static const uint16_t RC6_T = 444;
// Mark = LOW on wire (simulating TSOP receiving an IR burst)
void sendMark(uint16_t us) {
digitalWrite(IR_ARDUINO_PIN, LOW);
delayMicroseconds(us);
}
// Space = HIGH on wire (simulating TSOP idle)
void sendSpace(uint16_t us) {
digitalWrite(IR_ARDUINO_PIN, HIGH);
delayMicroseconds(us);
}
// Send a single RC-6 bit using Manchester encoding
void sendRC6Bit(uint8_t bit, uint8_t width) {
if (bit) {
sendMark(RC6_T * width);
sendSpace(RC6_T * width);
} else {
sendSpace(RC6_T * width);
sendMark(RC6_T * width);
}
}
// Send RC-6 Mode 6A (MCE) frame
void sendRC6_MCE(uint32_t data, uint8_t toggle) {
// Disable interrupts to ensure precise IR timing
noInterrupts();
// Leader: 6t mark + 2t space
sendMark(RC6_T * 6);
sendSpace(RC6_T * 2);
// Start bit: always 1
sendRC6Bit(1, 1);
// Mode bits: 1, 1, 0 (mode 6)
sendRC6Bit(1, 1);
sendRC6Bit(1, 1);
sendRC6Bit(0, 1);
// Toggle bit (double width = 2t per half-bit)
sendRC6Bit(toggle & 1, 2);
// 32 data bits, MSB first
for (int i = 31; i >= 0; i--) {
sendRC6Bit((data >> i) & 1, 1);
}
// Final return to idle state (HIGH)
sendSpace(1000);
// Re-enable interrupts
interrupts();
// Wait to let receiver process
delay(40);
}
uint32_t get_mce_code(uint8_t button) {
switch (button) {
case 1: return 0x800f041e; // UP
case 2: return 0x800f041f; // DOWN
case 3: return 0x800f0420; // LEFT
case 4: return 0x800f0421; // RIGHT
case 5: return 0x800f0422; // ENTER / OK
case 6: return 0x800f0423; // BACK
default: return 0;
}
}
void send_ir_for_button(uint8_t button, uint8_t toggle) {
uint32_t code = get_mce_code(button);
if (code == 0) return;
digitalWrite(LED_PIN, HIGH); // Turn debug LED ON
sendRC6_MCE(code, toggle);
digitalWrite(LED_PIN, LOW); // Turn debug LED OFF
}
byte b, n;
LinFrame frame;
unsigned long last_frame_time = 0;
uint8_t current_button = 0;
uint8_t toggle_bit = 0;
unsigned long last_ir_send_time = 0;
void process_button_state(uint8_t active_button) {
unsigned long now = millis();
if (active_button != 0) {
if (current_button == 0) {
// Button was just pressed!
current_button = active_button;
toggle_bit ^= 1; // Toggle bit alternates on each new press
send_ir_for_button(current_button, toggle_bit);
last_ir_send_time = now;
} else if (current_button == active_button) {
// Button is being held down!
// Send repeat code every 250ms
if (now - last_ir_send_time >= 250) {
send_ir_for_button(current_button, toggle_bit);
last_ir_send_time = now;
}
} else {
// A different button was pressed!
current_button = active_button;
toggle_bit ^= 1;
send_ir_for_button(current_button, toggle_bit);
last_ir_send_time = now;
}
} else {
// Idle state
current_button = 0;
}
last_frame_time = now;
}
void handle_frame() {
if (frame.get_byte(0) != SWM_ID)
return;
if (!frame.isValid())
return;
// Extract the data bytes
// SWM button frame has 4 data bytes
uint8_t d0 = frame.get_byte(1);
uint8_t d1 = frame.get_byte(2);
uint8_t active_button = 0;
if (d0 & 0x01) active_button = 1; // UP
else if (d0 & 0x02) active_button = 2; // DOWN
else if (d0 & 0x04) active_button = 3; // LEFT
else if (d0 & 0x08) active_button = 4; // RIGHT
else if (d1 & 0x08) active_button = 5; // ENTER / OK
else if (d1 & 0x01) active_button = 6; // BACK
process_button_state(active_button);
}
void setup() {
pinMode(IR_ARDUINO_PIN, OUTPUT);
digitalWrite(IR_ARDUINO_PIN, HIGH); // Idle state (HIGH)
pinMode(LED_PIN, OUTPUT);
digitalWrite(LED_PIN, LOW); // LED OFF
// Enable MCP2004
pinMode(CS_PIN, OUTPUT);
digitalWrite(CS_PIN, HIGH);
pinMode(FAULT_PIN, OUTPUT);
digitalWrite(FAULT_PIN, HIGH);
LINBusSerial.begin(9600);
frame = LinFrame();
last_frame_time = millis();
}
void loop() {
if (LINBusSerial.available()) {
b = LINBusSerial.read();
n = frame.num_bytes();
if (b == SYN_FIELD && n > 2 && frame.get_byte(n - 1) == 0) {
frame.pop_byte();
handle_frame();
frame.reset();
} else if (n == LinFrame::kMaxBytes) {
frame.reset();
} else {
frame.append_byte(b);
}
}
// Timeout: if no LIN frames received for 200ms, assume no button is pressed
if (millis() - last_frame_time > 200) {
current_button = 0;
}
}