diff --git a/README.md b/README.md index 3fbd8ce..7625682 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,13 @@ -# Xiaomi-Scooter-Motion-Control +# This is a fork! If you have already read this stuff in the original repo by PsychoMnts / Glenn, this is what was added in the README: +- [This fork](#this-fork) +- [Issues](#issues) +- [Releases](#releases) + +### **Please use the 3.4 release and configure baseThrottle (ln. 27) accordingly to the comments!** + +Thanks :) + +## Xiaomi-Scooter-Motion-Control Modification to legalise the Xiaomi Mi Scooters in The Netherlands. The idea is to make an small hardware modification on the Xiaomi scooters so they comply with the Dutch law. @@ -19,9 +28,11 @@ The best scooter to do this modification is the Xiaomi Mi Electric Scooter Essen - The motor is 250 watts - Max speed is 20 km/h, which is already fast to give push offs with your feet. +Check out [stepombouw.nl](https://stepombouw.nl) for more information and guides! + # Librarys -- https://github.com/contrem/arduino-timer +- https://github.com/contrem/arduino-timer (For 3.1 and 3.2) # How it works @@ -39,6 +50,8 @@ This firmware leaves behind the concept of different specified gears. Instead, t Another aspect that makes this firmware stand out is that it is able to detect kicks while the vehicle is throttling. It does so by storing and comparing an expected speed to the actual speed of the vehicle. If the actual speed exceeds expected speed by a defined integer, it is registered as a kick. +**The following section is configurable. Can be set to one kick aswell.** + One kick while throttling will reset the driving timer, two kicks will put the vehicle into INCREASINGSTATE. Whilst in INCREASINGSTATE, the speed of the vehicle can be increased (duh...). When you speed the vehicle by making a kick, the vehicle will adjust to that new speed. If a defined time has passed by without the driver making a new kick, the vehicle will be put back into DRIVINGSTATE and the driving timer will be started again. The vehicle will also be switched to INCREASINGSTATE when you first start driving or after you have released the break and increasing speed. @@ -46,7 +59,7 @@ Once the driving timer has expired, the throttle will be released. When you make # Issues -The Arduino software will make a warning about the amount of dynamic memory that is taken up by the firmware (88% on the nano). This is because currently, unfortunately, the script uses three different arduino-timer instances. We hope to fix this in a future release but for now it's not a big issue. Due to this issue we don't recommend setting the historySize option too high. +(3.4) Some false kicks may occur, we are working on the optimal calculatedSpeed in the ThrottleSpeed function. Additonally, a variant with PID support is in the making so that the vehicle will better adjust to the expected speed. # Releases @@ -63,12 +76,27 @@ The Arduino software will make a warning about the amount of dynamic memory that A few recommended values have been provided. +- V3.3 + + Skipped due to the amount of issues. + +- V3.4 + + - Added a option to set a minimum speed for when the minimumSpeedIncreasment will fire at first. + - Added baseThrottle and additionalSpeed to be able to easily adjust the calculatedSpeed within ThrottleSpeed. + - Added a kickDelay. This is the minimum time before another kick can be registered again. + - Switched to timers based on the millis() function. No memory exhaustion anymore! + - Added a DRIVEOUTSTATE to prevent false kicks after the boost has expired. + - Probably more changes, cleaned up a lot of stuff. + + # Other firmwares Feel free to try these other firmwares, I will try to include as many firmwares that are out there. - Glenn: https://github.com/PsychoMnts/Xiaomi-Scooter-Motion-Control - Jelle: https://github.com/jelzo/Xiaomi-Scooter-Motion-Control +- Job: https://github.com/mysidejob/step-support # Hardware diff --git a/Xiaomi-Scooter-Motion-Control_V3.4/Xiaomi-Scooter-Motion-Control_V3.4.ino b/Xiaomi-Scooter-Motion-Control_V3.4/Xiaomi-Scooter-Motion-Control_V3.4.ino new file mode 100644 index 0000000..02817f0 --- /dev/null +++ b/Xiaomi-Scooter-Motion-Control_V3.4/Xiaomi-Scooter-Motion-Control_V3.4.ino @@ -0,0 +1,389 @@ +#include +#include + +// WARNING: Always try to use the latest version available. +// Check here: https://github.com/kearfy/Xiaomi-Scooter-Motion-Control + +// +-============================================================================-+ +// |-================================ SETTINGS ==================================-| +// +-============================================================================-+ + +//Defines how much km/u is detected as a kick. +const int speedBump = 3; + +//Defines what the minimal increasement every kick should make. +const int minimumSpeedIncreasment = 5; + +//From which speed should the minimumSpeedIncreasment be activated. +const int enforceMinimumSpeedIncreasmentFrom = 16; + +//Defines how much percent the speedBump should go down per km in speed. (it's harder to speed up when driving 20km/u.) +const float lowerSpeedBump = 1; + +//Define the base throttle. between 0 and 45. +// ESSENTIAL & 1S: 0 +// PRO 1: 20 +// PRO 2: 45 +const int baseThrottle = 0; + +//Additional speed to be added on top of the requested speed. Leave at 4 for now. +const int additionalSpeed = 4; + +//Minimum and maximum speed of your scooter. (You can currently use this for just one mode) +const int minimumSpeed = 7; +const int maximumSpeed = 27; + +//Minimum speed before throtteling +const int startThrottle = 5; + +//Amount of kicks it takes to switch over the INCREASING state. +const int kicksBeforeIncreasment = 1; + +//Don't touch unless you know what you are doing. +const int breakTriggered = 47; + +//Defines how long one kick should take. +const int drivingTime = 5000; + +//Defines how much time the amount of kicks before increasment can take up. +const int kickResetTime = 2000; + +//Defines how much time should be in between two kicks. +const int kickDelay = 300; + +//Defines the amount of time the INCREASING state will wait for a new kick. +const int increasmentTime = 2000; + +//used to calculate the average speed. +const int historySize = 20; + +// Arduino pin where throttle is connected to (only pin 9 & 10 is ok to use) +const int THROTTLE_PIN = 10; +int LED_PCB = 13; + +//TX & RX pin +SoftwareSerial SoftSerial(2, 3); // RX, TX + +//END OF SETTINGS + +// +-============================================================================-+ +// |-=============================== VARIABLES ==================================-| +// +-============================================================================-+ +// |-========================== !!! DO NOT EDIT !!! =============================-| +// +-============================================================================-+ + +unsigned long currentTime = 0; +unsigned long drivingTimer = 0; +unsigned long kickResetTimer = 0; +unsigned long kickDelayTimer = 0; +unsigned long increasmentTimer = 0; +bool kickAllowed = true; + +int BrakeHandle; +int Speed; //current speed +int temporarySpeed = 0; +int expectedSpeed = 0; +int kickCount = 0; + +int historyTotal = 0; +int history[historySize]; +int historyIndex = 0; +int averageSpeed = 0; + +//motionmodes +uint8_t State = 0; +#define READYSTATE 0 +#define INCREASINGSTATE 1 +#define DRIVINGSTATE 2 +#define BREAKINGSTATE 3 +#define DRIVEOUTSTATE 4 + +void logByteInHex(uint8_t val) { + // if(val < 16) + // Serial.print('0'); + // + // Serial.print(val, 16); + // Serial.print(' '); +} + +uint8_t readBlocking() { + while (!SoftSerial.available()) { + delay(1); + } + + return SoftSerial.read(); +} + +void setup() { + pinMode(LED_PCB, OUTPUT); + + // initialize all the readings to 0: + for (int i = 0; i < historySize; i++) { + history[i] = 0; + } + + Serial.begin(115200); + SoftSerial.begin(115200); + + Serial.println("Starting Logging data..."); + + TCCR1B = TCCR1B & 0b11111001; //Set PWM of PIN 9 & 10 to 32 khz + + ThrottleWrite(45); +} + +uint8_t buff[256]; +void loop() { + int w = 0; + while (readBlocking() != 0x55); + + if (readBlocking() != 0xAA) { + return; + } + + uint8_t len = readBlocking(); + buff[0] = len; + + if (len > 254) { + return; + } + + uint8_t addr = readBlocking(); + buff[1] = addr; + + uint16_t sum = len + addr; + for (int i = 0; i < len; i++) { + uint8_t curr = readBlocking(); + buff[i + 2] = curr; + sum += curr; + } + + uint16_t checksum = (uint16_t) readBlocking() | ((uint16_t) readBlocking() << 8); + if (checksum != (sum ^ 0xFFFF)) { + return; + } + + for (int i = 0; i < len + 2; i++) { + logByteInHex(buff[i]); + } + + // Serial.print("check "); + // Serial.print(checksum, 16); + // + // Serial.println(); + switch (buff[1]) { + case 0x20: + switch (buff[2]) { + case 0x65: + BrakeHandle = buff[6]; + } + case 0x21: + switch (buff[2]) { + case 0x64: + if (buff[8] != 0) { + Speed = buff[8]; + } + } + } + + //Update the current index in the history and recalculate the average speed. + + historyTotal = historyTotal - history[historyIndex]; + history[historyIndex] = Speed; + historyTotal = historyTotal + history[historyIndex]; + historyIndex = historyIndex + 1; + + if (historyIndex >= historySize) { + historyIndex = 0; + } + + //Recalculate the average speed. + averageSpeed = historyTotal / historySize; + + motion_control(); + currentTime = millis(); + if (kickResetTimer != 0 && kickResetTimer + kickResetTime < currentTime && State == DRIVINGSTATE) resetKicks(); + if (increasmentTimer != 0 && increasmentTimer + increasmentTime < currentTime && State == INCREASINGSTATE) endIncrease(); + if (drivingTimer != 0 && drivingTimer + drivingTime < currentTime && State == DRIVINGSTATE) endDrive(); + if (kickDelayTimer == 0 || (kickDelayTimer != 0 && kickDelayTimer + kickDelay < currentTime)) { + kickAllowed = true; + kickDelayTimer = 0; + } else { + kickAllowed = false; + } +} + +void motion_control() { + if ((Speed != 0) && (Speed < startThrottle)) { + // If speed is under 5 km/h, stop throttle + ThrottleWrite(45); // 0% throttle + } + + if (BrakeHandle > breakTriggered) { + ThrottleWrite(45); //close throttle directly when break is touched. 0% throttle + digitalWrite(LED_PCB, HIGH); + if (State != BREAKINGSTATE) { + State = BREAKINGSTATE; + drivingTimer = 0; kickResetTimer = 0; increasmentTimer = 0; + Serial.println("BREAKING ~> Handle pulled."); + } + } else { + digitalWrite(LED_PCB, LOW); + } + + switch(State) { + case READYSTATE: + if (Speed > startThrottle) { + if (Speed > minimumSpeed) { + if (averageSpeed > Speed) { + temporarySpeed = averageSpeed; + } else { + temporarySpeed = Speed; + } + } else { + temporarySpeed = minimumSpeed; + } + + ThrottleSpeed(temporarySpeed); + State = INCREASINGSTATE; + Serial.println("INCREASING ~> The speed has exceeded the minimum throttle speed."); + } + + break; + case INCREASINGSTATE: + if (Speed >= temporarySpeed + calculateSpeedBump(temporarySpeed) && kickAllowed) { + kickCount++; + increasmentTimer = 0; + kickDelayTimer = currentTime; + } else if (averageSpeed >= Speed && kickCount < 1 && increasmentTimer == 0) { + increasmentTimer = currentTime; + } + + if (kickCount >= 1) { + if (Speed > temporarySpeed + calculateMinimumSpeedIncreasment(Speed)) { + temporarySpeed = ValidateSpeed(Speed); + } else { + temporarySpeed = ValidateSpeed(temporarySpeed + calculateMinimumSpeedIncreasment(Speed)); + } + + ThrottleSpeed(temporarySpeed); + } + + kickCount = 0; + break; + case DRIVINGSTATE: + if (Speed >= expectedSpeed + calculateSpeedBump(expectedSpeed) && kickAllowed) { + kickCount++; + drivingTimer = currentTime + increasmentTime; + kickResetTimer = currentTime; + kickDelayTimer = currentTime; + } + + if (kickCount >= kicksBeforeIncreasment) { + if (Speed > expectedSpeed + calculateMinimumSpeedIncreasment(Speed)) { + temporarySpeed = ValidateSpeed(Speed); + } else { + temporarySpeed = ValidateSpeed(expectedSpeed + calculateMinimumSpeedIncreasment(Speed)); + } + + ThrottleSpeed(temporarySpeed); + State = INCREASINGSTATE; + drivingTimer = 0; kickResetTimer = 0; + Serial.println("INCREASING ~> The least amount of kicks before increasment has been reached."); + } + + break; + case BREAKINGSTATE: + case DRIVEOUTSTATE: + if (BrakeHandle > breakTriggered) break; + if (Speed < startThrottle) { + State = READYSTATE; + Serial.println("READY ~> Speed has dropped under the minimum throttle speed."); + } else if (Speed >= averageSpeed + calculateSpeedBump(Speed)) { + if (State == DRIVEOUTSTATE) { + if (Speed > averageSpeed + calculateMinimumSpeedIncreasment(Speed)) { + temporarySpeed = ValidateSpeed(Speed); + } else { + temporarySpeed = ValidateSpeed(expectedSpeed); + } + } else { + temporarySpeed = ValidateSpeed(Speed); + } + + kickDelayTimer = currentTime; + ThrottleSpeed(temporarySpeed); + State = INCREASINGSTATE; + Serial.println("INCREASING ~> Break released and speed increased."); + } + + break; + default: + ThrottleWrite(45); + digitalWrite(LED_PCB, HIGH); + State = BREAKINGSTATE; + drivingTimer = 0; kickResetTimer = 0; increasmentTimer = 0; + Serial.println("BREAKING ~> Unknown state detected"); + } + +} + +int resetKicks() { + kickResetTimer = 0; + kickCount = 0; +} + +int endIncrease() { + expectedSpeed = temporarySpeed; + kickCount = 0; + State = DRIVINGSTATE; + drivingTimer = currentTime; + increasmentTimer = 0; kickResetTimer = 0; + Serial.println("DRIVING ~> The speed has been stabilized."); +} + +int endDrive() { + drivingTimer = 0; kickResetTimer = 0; + ThrottleWrite(45); + State = DRIVEOUTSTATE; + Serial.println("READY ~> Speed has dropped under the minimum throttle speed."); +} + +int calculateSpeedBump(int requestedSpeed) { + return round(speedBump * pow(lowerSpeedBump, requestedSpeed)); +} + +int calculateMinimumSpeedIncreasment(int requestedSpeed) { + return (requestedSpeed > enforceMinimumSpeedIncreasmentFrom ? minimumSpeedIncreasment : 0); +} + +int ValidateSpeed(int requestedSpeed) { + if (requestedSpeed < minimumSpeed) { + return minimumSpeed; + } else if (requestedSpeed > maximumSpeed) { + return maximumSpeed; + } else { + return requestedSpeed; + } +} + +int ThrottleSpeed(int requestedSpeed) { + if (requestedSpeed == 0) { + ThrottleWrite(45); + } else if (requestedSpeed == maximumSpeed) { + ThrottleWrite(233); + } else { + int throttleRange = 233 - 45; + float calculatedThrottle = baseThrottle + ((requestedSpeed + additionalSpeed) / (float) maximumSpeed * throttleRange); + ThrottleWrite((int) calculatedThrottle); + } +} + +int ThrottleWrite(int value) { + if (value < 45) { + analogWrite(THROTTLE_PIN, 233); + } else if (value > 233) { + analogWrite(THROTTLE_PIN, 233); + } else { + analogWrite(THROTTLE_PIN, value); + } +}