Freelander 1 Freelander EV

This site contains affiliate links for which LandyZone may be compensated if you make a purchase.
Yes mate, that's a good call, I rarely use the channel change buttons for the radio so could try them. The only issue that springs to mind is finding another input on the VCU to use but I'm sure there would be some way to manage it.
In the meantime I've spent more time playing with the settings and I'm getting pretty close to something acceptable so it's going down in the priority list as I still need to sort the charging.
There are spare lines in the clock spring even with the audio controls, as there's the unused (in most cases) cruise control lines.
There are cheapish universal kits available on AliExpress:
£73.41 | Steering Wheel Shift Paddles For Moza Thrustmaster Simulation Racing Game PC Rally Steering Wheel Center Control Box

Just for reference. Our Ioniq 5 gives exactly 100kW on full regen, so yours is very impressive.
 
There are spare lines in the clock spring even with the audio controls, as there's the unused (in most cases) cruise control lines.
There are cheapish universal kits available on AliExpress:
£73.41 | Steering Wheel Shift Paddles For Moza Thrustmaster Simulation Racing Game PC Rally Steering Wheel Center Control Box

Just for reference. Our Ioniq 5 gives exactly 100kW on full regen, so yours is very impressive.
Nice find mate, they look like fun. My MG gives around 100kW on full regen and maybe even more with the brake but I'm trying to be sensible and start out with a bit less with this setup.

However as I have the attention span of a nat, I've flip flopped back to the charging issues and have built a circuit to help with this.
This very complicated circuit uses a LM393 comparator and 5 resistors to detect when the J1772 plug is pushed into the socket, it provides a ground to an 8 way replay module to turn on the bits I need for charging. https://amzn.to/40LDJKC
I'm not using it to power the module, just to trigger it so the power demand is very low and no need for a diode.

PP_Sense.png

The original design has a 3.3k resistor on the 12V side of a voltage divider which worked fine on my desk but when I tested it in the car it triggered immediatley even without the cable plugged in so after a bit of messing about with a POT I figured out the 3.3k resistor needed replacing with a 255R resistor. As I didn't have one I used a 240R and 13R resistor in series. When I measured them with a meter they added up to just over 254 so I guess they are a tiny bit over the advertised resistance. The 2.7K resistor R3 is already in the J1772 socket so I didn't need to add it.

This is the finished article on a piece of strip board and it seems to work well. When I shove in the plug the relays trigger and when I push the release leaver on the J1772 plug to allow it to be pulled out the relays open.

IMG_20250203_145120687.jpg
 
Nice find mate, they look like fun. My MG gives around 100kW on full regen and maybe even more with the brake but I'm trying to be sensible and start out with a bit less with this setup.

However as I have the attention span of a nat, I've flip flopped back to the charging issues and have built a circuit to help with this.
This very complicated circuit uses a LM393 comparator and 5 resistors to detect when the J1772 plug is pushed into the socket, it provides a ground to an 8 way replay module to turn on the bits I need for charging. https://amzn.to/40LDJKC
I'm not using it to power the module, just to trigger it so the power demand is very low and no need for a diode.

View attachment 334437
The original design has a 3.3k resistor on the 12V side of a voltage divider which worked fine on my desk but when I tested it in the car it triggered immediatley even without the cable plugged in so after a bit of messing about with a POT I figured out the 3.3k resistor needed replacing with a 255R resistor. As I didn't have one I used a 240R and 13R resistor in series. When I measured them with a meter they added up to just over 254 so I guess they are not perfect resistors. The 2.7K resistor R3 is already in the J1772 socket so I didn't need to add it.

This is the finished article on a piece of strip board and it seems to work well. When I shove in the plug the relays trigger and when I push the release leaver on the J1772 plug to allow it to be pulled out the relays open.

View attachment 334436
I was thinking more that the paddles would be pretty easy thing to copy for your use.

Nice simple analogue circuit there Ali. The LM393 is a nice robust IC too, just what is needed in the hostile environment of a car.
 
I was thinking more that the paddles would be pretty easy thing to copy for your use.

Nice simple analogue circuit there Ali. The LM393 is a nice robust IC too, just what is needed in the hostile environment of a car.
Yes mate, paddles might be a project when I run out of other things to do. With my levels of procrastination ever increasing I'm not sure when that will happen. 🤣
 
So I've spent a bit more time on the charging and am finally getting close to sorting it.
As usual for me I didn't plan ahead very well so ended up with two circuit boards so this is what I've now got.

PXL_20250225_105741144.jpg


The circuit with the LM393 still detects when the EVSE is plugged in and I think I will use it to send GND to a latching relay to power the Arduino Nano and RTC module on the 2nd board. This way there should be minimal drain on the battery when the car is sitting idle. The reason for the latching relay is to ensure the control circuitry doesn't suddenly loose power should I pull out the charger plug. Doing so would basically crash everything possibly resulting in arcing at the charger port or damage to the charger.
Once plugged in the Nano starts running a sketch that checks for ground on PIN8 (from the LM393) and watches for a momentary ground on PIN9 (this would be from a button somewhere on the dash). If it sees GND on PIN8 and I push the button it will immediately turn on the relays in the correct sequence to power everything necessary for charging to begin.
If I don't push the button the Arduino will sit and wait until 2am then start charging and stop it at 8am.
When the charge cable is unplugged the Arduino will loose the ground on PIN8 and shut down the relays in the correct sequence, the final one being the latching relay which if I've thought this through correctly will remove power from the Arduino leaving just the LM393 module being powered.
I've mentioned before that typing out these updates really help me to work out the sequences and possible consequences and this one is no different.
As I typed it occurred that the Arduino would remain powered up after a night time charge so long as the cable is plugged in but I checked the power drain a while ago and it should only be around 25mA so shouldn't be a problem.
Now I need to sort the other major thing that occurred to me and that is to make the Arduino turn off the relays in the correct sequence so it's back to ChatGPT for me. 🤣

Edit: I also just realised I need a 7th relay as the 6 way module has been filled and I now need another to latch the power. :rolleyes:

Edit 2: It just occurred I need to rethink the logic as I need a fail safe to stop the car being driven away with the cable still plugged in and I was going to use one of the module relays for this.
My head hurts! :oops:

Edit 3: Maybe If I used the latching relay to send GND to the throttle pedal when the Plug is not in and to the Arduino when it is, then it would not only be a fail safe but would free up one relay and I can get away with 6 way module.
Mmmmmm???
 
Last edited:
It's t
lol, fun project within the project.
It's the way I've approached the whole thing from 2020.
I started with a vague idea what to do, broke the whole thing down into bite sized projects and worked on each as I came to it.
It resulted in mistakes and extra work from time to time but made the project possible.
 
I've got a little further with charge control
As always I need to write this out cause my brain is way to small to think this through and every time I think it's ready to wire into the car I realise I've forgotten about some issue that could crop up.
Having now spent a couple of hours going through the scenarios below I need to go back to the Arduino sketch and tweak it a bit to add a control line on PIN10 and add a power output for the Due.
I'll probably need to make more changes when I realise I've cocked something else up but for now this is my thinking.
BTW this is a long way from being an elegant solution but I don't have the programming skills to rewrite the code in the VCU or the charge control Arduino Due so if this works it will have to do.


Current Setup
Plug in the cable
Turn on ignition to power up all devices.
The VCU sends GND to the charger to power it up
An Arduino Due checks the voltage from an Isabellenhutte shunt using CAN. If the voltage is within a preset range the Due closes the CP line using a relay (CP Relay) to allow charging to begin. This Arduino Due also sends Voltage, current and power info to the Android head unit over WiFi.
When the Due sees the voltage above a certain level it turns off the relay which disconnects CP and stops charging.
A backup safety is provided by the VCU which will power off the charger if it sees an over volt situation.

New Setup
Scenario 1 - Charging starts and completes

Plug in the charger cable.
LM393 detects EVSE present from PP and sends GND to a relay (ChargeEnable) and PIN8 on the Nano.
The ChargeEnable relay sends GND to power the Nano and at the same time removes GND from Throttle pedal to prevent driving off while plugged in.
The Nano powers the Due using a MOSFET or single relay module then pauses for 2 seconds. This happens only once when the Nano is turned on.
If the voltage is within a preset range the Due closes the CP relay to enable charging to begin.
The CP relay as well as connecting the CP to the charger sends GND to PIN10 of the Nano using 2nd set of contacts.
We push the button to start charging (momentary GND to PIN9) or it starts automagically at 2am.
The Nano checks for PIN8 and PIN10 to GND and if so activates the relays with the last relay sending a second (latching) ground to power the Nano. This is to prevent the Nano crashing should the plug be pulled.
Charging completes and the Arduino Due releases the CP relay to stop the charger.
The relay also disconnects GND from PIN10 so the Nano turns off the Module relays and removes power from the Due.
The Nano will still receive power from the LM393 as the EVSE is still plugged in and the Charge Enable Relay is still closed but this should be OK as even with the relay and LM393 they should only draw around 100 to 150mA with the module relays turned off.


Scenario 2 - Plug pulled while charging
Everything as above.
If the Plug is pulled while the car is charging the LM393 removes GND from the ChargeEnable relay and PIN8 of the Nano
The Nano still has GND through the last relay on the module so remains powered on.
Because the Nano has lost GND on PIN8 it turns off the relays.
When the last relay turns off it removes GND from the Nano which powers it off.

The critical thing both now and when this is implemented is that the battery should never be allowed to charge above 400V.
The Due I use to control this gets it's voltage from the shunt and the VCU which is a fail safe gets it's voltage from the Inverter so hopefully I've mitigated as best I can.
 
Last edited:
Phew! After many not fun hours fighting with ChatGPT and Deepseek, I finally have a working software solution, if anybody is mad enough to have a look the code is below.
Basically I gave up on using a MOSFET module as neither ChatGPT nor Deepseek could figure out the logic required to turn it on when the Nano boots, and keep it on until the relays are all turned off, so I switched to using a two way relay module instead. I figured we were already using a relay module enabled by the Arduino pins going low so this would simplify things.
ChatGPT still couldn't make it work correctly and often messed up the bits of code that were working so I gave up on it and tried Deepseek which was much better. It seemed more able to make the changes I wanted without screwing up the rest of the code and eventually gave me a working program.

I only understand the basics of Arduino code but essentially this is what happens.
When I plug in the charger cable the Arduino Nano is turned on by the LM393.
The Nano turns on relays 7 and 8 when it boots turning on the Arduino Due.
It then sits waiting for grounds to come from PP and CP,
If both are present and I don't push the charge now button it will do nothing until 2am, then turn on relays 1 to 6 starting the charging.
If the pack voltage reaches a preset level the Arduino Due releases CP and the Nano turns off all relays stopping charging.
If the car is still charging at 8am all relays are turned off stopping charging.
If I had pushed the charge now button the Nano would have turned on relays 1 to 6 starting charging and should continue until complete or 8am.


Code:
#include <Wire.h>
#include <RTClib.h>
#include <EEPROM.h>

constexpr int NUM_RELAYS = 8; // 8 relays (1-6 for logic, 7-8 for always-on until 1-6 turn off)
constexpr unsigned long STATUS_UPDATE_INTERVAL = 5000;
constexpr int SERIAL_BAUD_RATE = 9600;
constexpr int RELAY_ACTIVE_STATE = LOW;  // Relays are active low
constexpr int RELAY_INACTIVE_STATE = HIGH; // Relays are inactive high

struct TimeSettings {
    int8_t hour;
    int8_t minute;
};

struct EEPROMConfig {
    static constexpr int START_HOUR = 0;
    static constexpr int START_MINUTE = 1;
    static constexpr int END_HOUR = 2;
    static constexpr int END_MINUTE = 3;
};

class RelayController {
private:
    RTC_DS3231 rtc;
    const int relayPins[NUM_RELAYS] = {2, 3, 4, 5, 6, 7, 8, 9}; // Relays 1-6: pins 2-7, Relays 7-8: pins 8-9
    const int EVSEPresent = 10; // EVSE present signal
    const int CPPresent = 11;   // CP present signal
    const int overridePin = 12; // Override button pin
    TimeSettings startTime = {2, 0};
    TimeSettings endTime = {8, 0};
    bool evsePresent = false;
    bool cpPresent = false;
    bool overrideTriggered = false;
    unsigned long lastStatusUpdate = 0;
    bool relaysAreOn = false;

    bool isValidTime(int hour, int minute) {
        return hour >= 0 && hour < 24 && minute >= 0 && minute < 60;
    }

    void loadFromEEPROM() {
        EEPROM.get(EEPROMConfig::START_HOUR, startTime);
        EEPROM.get(EEPROMConfig::END_HOUR, endTime);
        if (!isValidTime(startTime.hour, startTime.minute)) startTime = {2, 0};
        if (!isValidTime(endTime.hour, endTime.minute)) endTime = {8, 0};
    }

    void saveToEEPROM() {
        EEPROM.put(EEPROMConfig::START_HOUR, startTime);
        EEPROM.put(EEPROMConfig::END_HOUR, endTime);
    }

    void setRelayStates(bool active) {
        if (active) {
            // Activate relays 1-6
            for (int i = 0; i < 6; i++) {
                digitalWrite(relayPins[i], RELAY_ACTIVE_STATE);
                delay(500); // Delay between each relay activation
            }
            relaysAreOn = true;
        } else {
            // Deactivate relays 1-6
            for (int i = 5; i >= 0; i--) {
                digitalWrite(relayPins[i], RELAY_INACTIVE_STATE);
                delay(500); // Delay between each relay deactivation
            }
            relaysAreOn = false;

            // Turn off relays 7-8 only after relays 1-6 are deactivated
            digitalWrite(relayPins[6], RELAY_INACTIVE_STATE); // Relay 7
            digitalWrite(relayPins[7], RELAY_INACTIVE_STATE); // Relay 8
        }
    }

    bool isTimeInRange(const DateTime& now) {
        int currentMinutes = now.hour() * 60 + now.minute();
        int startMinutes = startTime.hour * 60 + startTime.minute;
        int endMinutes = endTime.hour * 60 + endTime.minute;
        return (startMinutes <= endMinutes) ? (currentMinutes >= startMinutes && currentMinutes < endMinutes)
                                            : (currentMinutes >= startMinutes || currentMinutes < endMinutes);
    }

public:
    void begin() {
        Serial.begin(SERIAL_BAUD_RATE);

        // Initialize RTC
        if (!rtc.begin()) {
            Serial.println(F("RTC initialization failed!"));
            while (1) delay(800);
        }
        if (rtc.lostPower()) {
            Serial.println(F("RTC lost power, resetting to compile time."));
            rtc.adjust(DateTime(F(__DATE__), F(__TIME__)));
        }

        // Initialize all relays and ensure they start in the correct state
        for (int i = 0; i < NUM_RELAYS; i++) {
            pinMode(relayPins[i], OUTPUT);
            digitalWrite(relayPins[i], RELAY_INACTIVE_STATE); // Start with all relays OFF
        }

        // Turn on relays 7-8 immediately
        digitalWrite(relayPins[6], RELAY_ACTIVE_STATE); // Relay 7
        digitalWrite(relayPins[7], RELAY_ACTIVE_STATE); // Relay 8

        // Initialize input pins
        pinMode(EVSEPresent, INPUT_PULLUP);
        pinMode(CPPresent, INPUT_PULLUP);
        pinMode(overridePin, INPUT_PULLUP);

        // Load time settings from EEPROM
        loadFromEEPROM();
    }

    void update() {
        DateTime now = rtc.now();
        evsePresent = digitalRead(EVSEPresent) == LOW;
        cpPresent = digitalRead(CPPresent) == LOW;
        bool overridePressed = digitalRead(overridePin) == LOW;

        if (!evsePresent || !cpPresent) {
            overrideTriggered = false;
            if (relaysAreOn) {
                setRelayStates(false); // Turn off relays 1-6 (and 7-8 if relays 1-6 are off)
            }
        } else {
            if (evsePresent && cpPresent && overridePressed) {
                overrideTriggered = true;
            }

            if (overrideTriggered) {
                setRelayStates(true); // Turn on relays 1-6
            } else {
                bool shouldActivate = evsePresent && cpPresent && isTimeInRange(now);
                if (shouldActivate && !relaysAreOn) {
                    setRelayStates(true); // Turn on relays 1-6
                } else if (!shouldActivate && relaysAreOn) {
                    setRelayStates(false); // Turn off relays 1-6 (and 7-8 if relays 1-6 are off)
                }
            }
        }

        // Process serial commands
        if (Serial.available()) {
            processCommand(Serial.readStringUntil('\n'));
        }

        // Print status periodically
        if (millis() - lastStatusUpdate >= STATUS_UPDATE_INTERVAL) {
            printStatus(now);
            lastStatusUpdate = millis();
        }
    }

    void processCommand(String command) {
        command.trim();
        if (command.startsWith("start ")) {
            parseTime(command.substring(6), startTime);
            saveToEEPROM();
            Serial.println(F("Start time updated."));
        } else if (command.startsWith("end ")) {
            parseTime(command.substring(4), endTime);
            saveToEEPROM();
            Serial.println(F("End time updated."));
        } else if (command.startsWith("set ")) {
            setRTC(command.substring(4));
            Serial.println(F("RTC time updated."));
        } else if (command == "status") {
            printStatus(rtc.now());
        } else if (command == "help") {
            printHelp();
        } else {
            Serial.println(F("Unknown command. Type 'help' for commands."));
        }
    }

    void parseTime(String input, TimeSettings& timeSetting) {
        int hour, minute;
        if (sscanf(input.c_str(), "%d:%d", &hour, &minute) == 2 && isValidTime(hour, minute)) {
            timeSetting.hour = hour;
            timeSetting.minute = minute;
        } else {
            Serial.println(F("Invalid time format. Use HH:MM."));
        }
    }

    void setRTC(String input) {
        int year, month, day, hour, minute, second;
        if (sscanf(input.c_str(), "%d-%d-%d %d:%d:%d", &year, &month, &day, &hour, &minute, &second) == 6) {
            rtc.adjust(DateTime(year, month, day, hour, minute, second));
        } else {
            Serial.println(F("Invalid format. Use YYYY-MM-DD HH:MM:SS."));
        }
    }

    void printStatus(const DateTime& now) {
        Serial.println(F("\n=== System Status ==="));
        Serial.print(F("Current: "));
        Serial.print(now.timestamp());
        Serial.print(F("\nStart: "));
        Serial.print(startTime.hour);
        Serial.print(F(":"));
        Serial.print(startTime.minute);
        Serial.print(F("\nEnd: "));
        Serial.print(endTime.hour);
        Serial.print(F(":"));
        Serial.print(endTime.minute);
        Serial.print(F("\nEVSE Present? "));
        Serial.println(evsePresent ? F("Yes") : F("No"));
        Serial.print(F("CP Present? "));
        Serial.println(cpPresent ? F("Yes") : F("No"));
        Serial.print(F("Override Active? "));
        Serial.println(overrideTriggered ? F("Yes") : F("No"));
        for (int i = 0; i < NUM_RELAYS; i++) {
            Serial.print(F("Relay "));
            Serial.print(i + 1);
            Serial.print(F(" Status? "));
            Serial.println(digitalRead(relayPins[i]) == RELAY_ACTIVE_STATE ? F("ON") : F("OFF"));
        }
        Serial.print(F("Temperature: "));
        Serial.print(rtc.getTemperature());
        Serial.println(F("°C"));
    }

    void printHelp() {
        Serial.println(F("\nAvailable Commands:"));
        Serial.println(F("  start HH:MM    - Set relay start time"));
        Serial.println(F("  end HH:MM      - Set relay end time"));
        Serial.println(F("  set YYYY-MM-DD HH:MM:SS - Set RTC time"));
        Serial.println(F("  status         - Show current status"));
        Serial.println(F("  help           - Show help message\n"));
    }
};

RelayController controller;

void setup() {
    controller.begin();
}

void loop() {
    controller.update();
}
 
Last edited:
It turns out if you want AI to write your code you need to give it VERY specific requirements. Sometimes you even need to suggest where it might have gone wrong for it to repair it's mistakes. After many hours staring at the damn thing it occurred to me the approximate area where the issue was and suddenly Deepseek was able to see the issue and fix it. I'm not convinced ChatGPT would ever have got there.

Now I have to go to the car and wire the damn thing in :oops:
 
It turns out if you want AI to write your code you need to give it VERY specific requirements. Sometimes you even need to suggest where it might have gone wrong for it to repair it's mistakes. After many hours staring at the damn thing it occurred to me the approximate area where the issue was and suddenly Deepseek was able to see the issue and fix it. I'm not convinced ChatGPT would ever have got there.

Now I have to go to the car and wire the damn thing in :oops:
Maybe it might be quicker overall to learn a little more about coding Arduino than learning how to code the 'AI' ;)
 
Maybe it might be quicker overall to learn a little more about coding Arduino than learning how to code the 'AI' ;)
If I was doing this kind of stuff every day or even every week then yes I agree but it might be 6 months before I'm back at it and I'll be round the fish bowl waaaaay too often to remember anything I've learned.
And anyway I tried to learn programming when I was a teenager and in my twenties and was rubbish then. I'll be 60 next birthday so the chances of me learning complicated programming now are slim, my brain just doesn't work that way. I've watched guys who are good at it and I know I could never compete so instead I'll just carry on cheating. 🤣
 
Last edited:
Just make sure the AI doesn't add something for it's own purposes !!

Skynet's coming

Too late! Sorry guys but I think I may have started WW3! :eek:
And we can't even blame the Donald! :oops:

Once again @Alibro I'm very impressed with what you're doing here. Adding bespoke coded devices is next level stuff.
Thanks mate, I've been into electronics and computers since I was 15 and been programming off and on ever since so this stuff is what I enjoy doing.
I've never been very good at it compared to wizz kids but better than the average person so I'm kinda inbetween. Good enough to mostly not be dangerous but not good enough to make any money at it. 😂
 
So I've spent a bit more time on the charging and am finally getting close to sorting it.
As usual for me I didn't plan ahead very well so ended up with two circuit boards so this is what I've now got.

View attachment 335829

The circuit with the LM393 still detects when the EVSE is plugged in and I think I will use it to send GND to a latching relay to power the Arduino Nano and RTC module on the 2nd board. This way there should be minimal drain on the battery when the car is sitting idle. The reason for the latching relay is to ensure the control circuitry doesn't suddenly loose power should I pull out the charger plug. Doing so would basically crash everything possibly resulting in arcing at the charger port or damage to the charger.
Once plugged in the Nano starts running a sketch that checks for ground on PIN8 (from the LM393) and watches for a momentary ground on PIN9 (this would be from a button somewhere on the dash). If it sees GND on PIN8 and I push the button it will immediately turn on the relays in the correct sequence to power everything necessary for charging to begin.
If I don't push the button the Arduino will sit and wait until 2am then start charging and stop it at 8am.
When the charge cable is unplugged the Arduino will loose the ground on PIN8 and shut down the relays in the correct sequence, the final one being the latching relay which if I've thought this through correctly will remove power from the Arduino leaving just the LM393 module being powered.
I've mentioned before that typing out these updates really help me to work out the sequences and possible consequences and this one is no different.
As I typed it occurred that the Arduino would remain powered up after a night time charge so long as the cable is plugged in but I checked the power drain a while ago and it should only be around 25mA so shouldn't be a problem.
Now I need to sort the other major thing that occurred to me and that is to make the Arduino turn off the relays in the correct sequence so it's back to ChatGPT for me. 🤣

Edit: I also just realised I need a 7th relay as the 6 way module has been filled and I now need another to latch the power. :rolleyes:

Edit 2: It just occurred I need to rethink the logic as I need a fail safe to stop the car being driven away with the cable still plugged in and I was going to use one of the module relays for this.
My head hurts! :oops:

Edit 3: Maybe If I used the latching relay to send GND to the throttle pedal when the Plug is not in and to the Arduino when it is, then it would not only be a fail safe but would free up one relay and I can get away with 6 way module.
Mmmmmm???
Looks like I cocked up somewhere. :mad:
While testing in the car I seem to have blown two digital inputs in the Nano so am now rebuilding the circuit. I'm not certain how it happened but I probably stuck 12V up it's arse and it didn't like it so it'll to be easier to do it again than to try and desolder the Nano and solder a new one in. :rolleyes:
 
BTW I'm not laughing at your misfortune just your description of what happened. I do absolutely admire what you've achieved,with a great dollop of envy, because I wouldn't know where to start & I'll admit I fall asleep trying to understand some of that coding malarkey 🙂. Keep up the good work & the down to earth descriptions of when it doesn't go right, all the best.
 
Back
Top