//Bron:https://www-pedelecforum-de.translate.goog/forum/index.php?threads/elfkw-umbau-mit-programmierbarem-arduino-controller.11188/&_x_tr_sl=de&_x_tr_tl=en&_x_tr_hl=en-US&_x_tr_pto=nui
//https://learn.sparkfun.com/tutorials/current-sensor-breakout-acs723-hookup-guide?_ga=2.76794965.1125107729.1637323478-701404252.1635898583
// https://forum.arduino.cc/t/acs712-30a-current-sensor/642512/24 and very good: https://www.engineersgarage.com/acs712-current-sensor-with-arduino/
//----------------------------------------- FOR TESTING ----
//#include <LiquidCrystal.h>
#include <LiquidCrystal_I2C.h>
//#include <EEPROM.h>
//#include "EEPROMAnything.h"
//LiquidCrystal lcd(12, 11, 7, 8, 9, 10);
#define I2C_ADDR 0x27
#define LCD_COLUMNS 20
#define LCD_LINES 4
LiquidCrystal_I2C lcd(I2C_ADDR, LCD_COLUMNS, LCD_LINES);
struct savings {
float voltage;
float capacity;
};
savings variable = {0.0,0.0}; //variable stores last voltage and capacity read from EEPROM
//Config Options-----------------------------------------------------------------------------------------------------
int throttle_out = 3; // Throttle out-Pin -- D3
int throttle_in = A3; // Throttle read-Pin --A3
int pas_in = 13; //PAS Sensor read-Pin--D13
int poti_in = A0; //PAS Speed-Poti-Pin --A0
int switch_1 = 2; //Mode_Switch read-Pin -- D2
int voltage_in = A2; //Voltage read-Pin --A2
int current_in = A1; //Current read-Pin --A1
int speedscale = 90; //0..100% Speed Scale
boolean startingaidenable = true; //enable starting aid?
float vcutoff = 33.0; //cutoff voltage off my battery;
//Variable-Declarations-----------------------------------------------------------------------------------------------
int throttle_stat = 0; //Throttle reading
int throttle_write=0; //Throttle write value
int pas_on_time = 0; //High-Time of PAS-Sensor-Signal (needed to determine pedaling direction)
int pas_off_time = 0; //Low-Time of PAS-Sensor-Signal (needed to determine pedaling direction)
int pas_failtime = 0; //how many subsequent "wrong" PAS values?
int mode = 0; //Mode: 1= Throttle, 2= PAS
int power_set = 0; //Set Power
float battery_percent = 0; //battery capacity
float current = 0; //measured battery current
float voltage = 0; //measured battery voltage
float voltage_old = 0;
float voltage_display = 0; //averaged display voltage
float power = 0; //calculated power
float factor_power = 1; //factor controling the power
float factor_volt = 1; //factor controling voltage cutoff
float poti_stat; //PAS-Poti setting
float mah = 0; //milliamperehours drawn from battery;
unsigned long last_pas_event = millis(); //last change-time of PAS sensor status
unsigned long last_writetime = millis(); //last time display has been refreshed
unsigned long last_writetime_long = millis(); //last time capacity has been saved
boolean pas_stat = false; //current PAS sensor status
boolean pas_stat_old = false; //last PAS sensor status
boolean pedaling = false; //pedaling? (in forward direction!)
boolean switch1_stat = false; //status of switch 1
boolean firstrun = true; //Set first run of loop to true to do it the first time?
//Setup---------------------------------------------------------------------------------------------------------------------
void setup()
{
Serial.begin(9600);
pinMode(throttle_out, OUTPUT); // Throttle out-Pin -- D3
pinMode(pas_in, INPUT); //PAS Sensor read-Pin--D13
pinMode(switch_1, INPUT); //Mode_Switch read-Pin -- D2
//lcd.begin(8, 2);
//EEPROM_readAnything(0,variable);
//I2C code
lcd.init();
lcd.backlight();
//Show what was saved
/*
voltage_display = variable.voltage;
mah = variable.capacity;
Serial.print("B_Volts_mem: ");
Serial.print(voltage_display,1);
Serial.print("V ");
Serial.print("mAh_Used_mem: ");
Serial.println(mah,0);
*/
// Print something
/*lcd.setCursor(3, 0);
lcd.print("Hello, world!");
lcd.setCursor(2, 1);
lcd.print("Wokwi Online IoT");
lcd.setCursor(5, 2);
lcd.print("Simulator");
lcd.setCursor(7, 3);
lcd.print("Enjoy!");
delay(2000);
*/
// clear the screen
lcd.clear();
}
//================= MAIN PROGRAM LOOP STARS HERE ========================================================================
void loop()
{
//Readings-----------------------------------------------------------------------------------------------------------------
pas_stat = digitalRead(pas_in); //PAS sensor status -- D13
//poti_stat = analogRead(poti_in); // 0...1023 pin A0. This is POT to set the power going to the motor.
poti_stat = 1023.0; //Comment out if testing is done: this is only for testing. Full blast = 500 Watts motor
//throttle_stat = (analogRead(throttle_in) - 183) * 1.5; // 0...1023. Throttle is read on A3
//throttle_stat = (840 - 183)* 1.5; //Comment out if testing has been done. This is the RAW analog value read from the throttle.
//New way of reading the throttle
//get the pwm value to use on the Mosfet to drive the motor speed
int sensorMin = 40;
int sensorMax = 840;
throttle_stat = map(analogRead(throttle_in), sensorMin, sensorMax, 0, 255); //Read setpoint for throttle on A1
//reading a 1-4v hall affect throttle -> int pwmValueToMosfet = map(throttleInPin,40,840,0,255); // this maps the hall throttle range between 0 and 255.
//for testing
//throttle_stat = map(1023, sensorMin, sensorMax, 0, 255); //Read setpoint for throttle on A1
//switch1_stat = digitalRead(switch_1); //Pin D2 (assign Pin D2's value as true or false) => boolean switch1_stat = false; initial status of switch 1
// if Pin D2 = HIGH, -> true / if Pin D2 = LOW -> false
//For testing
switch1_stat = true; //means that Pin D2 = HIGH. Also means that Mode will be set to 1 which is throttle mode
//switch1_stat = false;
//Test code:
int RawAnaVoltage = 500; //uncomment for testing
int RawAnaCurrent = 100; //uncomment for testing only
voltage = RawAnaVoltage * 5.0/0.06369/1024.0 + 0.4; //Read the battery Voltage (Assume that we have a stable 5v supply)
current = RawAnaCurrent * 5.0/0.0366/1024.0; //Read the current consumption
//Line 0
lcd.setCursor(0, 0); //pos, line
lcd.print("B/V:");
lcd.setCursor(5, 0); //pos, line
lcd.print(voltage,2);
//Line 2
lcd.setCursor(0, 1);
lcd.print("A:");
lcd.setCursor(3, 1);
lcd.print(current,2);
//Line 3
lcd.setCursor(0, 2);
lcd.print("Throt:");
lcd.setCursor(7, 2);
lcd.print(throttle_stat);
//Line 4
lcd.setCursor(0, 3);
lcd.print("SOC:");
lcd.setCursor(5, 3);
lcd.print(battery_percent,0);
lcd.setCursor(8, 3);
lcd.print("Ah/Use:");
lcd.print(mah,0);
//Comment out the next 2 lines if the above test code are being used.
//voltage = analogRead(voltage_in) * 5.0/0.06369/1024.0 + 0.4; //Read the battery Voltage
//current = analogRead(current_in) * 5.0/0.0366/1024.0; //Read the current consumption
//Check if Battery was charged since last power down-----------------------------------------------------------------------
if (firstrun==true){
if (variable.voltage > (voltage - 2)){ //charging detected if voltage is 2V higher than last stored voltage
mah=variable.capacity;
}
}
firstrun=false; //now turn first run off by making setting it as false.
//Calculations------------------------------------------------------------------------------------------------------------
voltage_display = 0.999 * voltage_display + 0.001 * voltage; //averaged voltage for display
power = current * voltage;
//For debugging
Serial.print("Power: ");
Serial.print(power,2);
//Set profile => Mode: 1= Throttle, 2= PAS
if (switch1_stat == false) //switch is LOW
{mode=2;} //Then Mode is = 2
else
{mode=1;} //Else Mode is = 1
//Power control: if power is too high, slowly decrease the factor and vice versa----------------------
power_set = poti_stat/1023.0 * 500; //poti stat = the raw analog value read on Pin A0
//for debugging
Serial.print(" Power_Set: ");
Serial.print(power_set);
if (power > power_set) //power here is: (current * voltage), previously calculated above;
{factor_power = factor_power * 0.9997;}
else
{factor_power = factor_power * 0.9997 + 0.0003;}
if (power_set == 0)
{factor_power = 0;}
if (pedaling == false)
{factor_power = 0;}
//for debugging
//Serial.print(" Power Factor: ");
//Serial.println(factor_power,2);
/*
Constrain Syntax:
constrain(x, a, b)
Parameters:
x: the number to constrain Allowed data types: all data types.
a: the lower end of the range. Allowed data types: all data types.
b: the upper end of the range. Allowed data types: all data types.
Returns:
x: if x is between a and b.
a: if x is less than a.
b: if x is greater than b.
*/
factor_power = constrain(factor_power,0,1);
//for debugging
Serial.print(" Power Factor: ");
Serial.print(factor_power,2);
//Voltage cutoff----------------------------------------------------------------------------------------------------------
if (voltage < vcutoff) //V Cutoff is currently set at 33,3V
{factor_volt = factor_volt * 0.9997;}
else
{factor_volt = factor_volt * 0.9997 + 0.0003;}
//for debugging
Serial.print(" Volt Factor: ");
Serial.print(factor_volt,2);
//Are we pedaling=?-----------DE-activate PAS -------------------------------------------------------------------------------------------
/*
if (pas_stat != pas_stat_old)
{
if (pas_stat==true)
{pas_off_time=millis()-last_pas_event;}
else
{pas_on_time=millis()-last_pas_event;}
pas_stat_old = pas_stat;
last_pas_event = millis();
pas_failtime=pas_failtime+1;
}
if ((pas_on_time > 1.2 * pas_off_time)&&(pas_on_time < 3 * pas_off_time)) //when pedaling forward the pas_on_time is with my sensor approximately 2 times the pas_off_time......
{
pedaling=true;
pas_failtime=0;
}
if ((millis()-last_pas_event) > 500)
{pedaling = false;}
if (pas_failtime > 0) //increase to make robust against PAS faults
{pedaling=false;}
*/
//Added
pedaling = true; //simulate that we are pedaling
//Throttle output---------------------------------------------------------------
if (pedaling==true){
//For debugging ONLY!
Serial.print(" Throt_stat: ");
Serial.println(throttle_stat);
if (throttle_stat > 1023 * factor_power) //Throttle mode/pushed Hrottle_stat = analog value read from the throttle => throttle_stat = (830 - 183)* 1.5;
{throttle_write = throttle_stat;}
else
{throttle_write = 1023 * factor_power;} //no throttle pushed
}
/*
else
{
if ((mode == 2)&&(startingaidenable==true)) //Starting aid activated is initially set to true
{throttle_write = constrain(throttle_stat,0,300);} //starting aid up to approx. 6 km/
else
{throttle_write = 0;} //no starting aid
}
*/
throttle_write = throttle_write * 0.15 * speedscale/100.0 * factor_volt + 46;
//For debugging ONLY!
//Serial.print("M_PWM: ");
//Serial.println(throttle_write,1);
//send PWM value to the MOTOR
analogWrite(throttle_out,throttle_write); //write PWM value to pin D3
//Show something on the LCD---------------------------------------------------------------------------------
if (millis()-last_writetime > 500) //don't do this more than twice a second
{
if (voltage_display > 38.6)
{battery_percent = -15.92628 + 0.71422 * voltage_display - 0.007398 * pow(voltage_display,2);}
else
{
if (voltage_display > 36.76)
{battery_percent = 5414.20057 - 431.39368 * voltage_display + 11.449212 * pow(voltage_display,2) - 0.1012069 * pow(voltage_display,3);}
else
{battery_percent = 0.0025 * pow(voltage_display - 33,3);}
}
battery_percent = constrain(battery_percent*100,0,100); //Battery SOC
mah = mah + current * (millis()-last_writetime)/3600.0; //battery usage in maH
/*
lcd.setCursor(0,0);
lcd.print(voltage_display,1);
lcd.print("V ");
lcd.setCursor(5,0);
lcd.print((int)constrain(battery_percent,0,100));
lcd.print("% ");
lcd.setCursor(0,1);
lcd.print(int(power));
lcd.print("W ");
lcd.setCursor(4,1);
lcd.print(mah,0);
*/
//Serial OutPut
Serial.print("B_Volts: ");
Serial.print(voltage_display,1);
Serial.print("V ");
Serial.print("Amps: ");
Serial.print(current,2);
Serial.print("A ");
Serial.print("Watts(I*V): ");
Serial.print(int(power));
Serial.print("W ");
Serial.print("B_Capacity: ");
Serial.print((int)constrain(battery_percent,0,100));
Serial.print("% ");
Serial.print("mAh_Use: ");
Serial.print(mah,0);
//Show profile => Mode: 1= Throttle, 2= PAS
if (switch1_stat == false){ //switch is LOW
Serial.print(" Mode:2");
}
else{
Serial.print(" Mode:1");
}
Serial.print(" M_PWM: ");
Serial.println(throttle_write);
last_writetime=millis();
}
//Save Data to to EEPROM
/*
We use EEPROM.write function to store data to the memory. Each place is of 8 bits so values from 0 to 254. If you want to store a number higher
than 254 you need to save the number into two positions and then merge the values together. For example, in the code below if you
make EEPROM.write(0, number_to_save); since number_to_save is 375 it will store to the 0 position the number (375-254) = 121, which is the overflow
from the maximum of 254. So we will have to find a solution of how to store higher numbers than 254 and we will see that in the next part.
Using EEPROM.read(), we can then read the byte of a specific position. Again, if our number is too high and divided into 2 or
more bytes, we have to make multiple EEPROM.read() in order to get all the data.
When saving a number from 0-255, EEPROM.write is sufficient. When saving a larger number, you have to call the EEPROM routines more than
once, to save a "high byte" and a "low byte" for 16-bit numbers, or even more often for bigger numbers or other data types that cannot fit in one byte.
With the following code, you can write and read any data structure or variable, using any number of bytes of EEPROM to do it (of course, only as many
bytes as your chip holds), all in a single call. A common use of this would be to have a group of related "persistent" variables that you
want to save for future sessions. This could be a high-score list in a game, or a set of configuration choices in a device like an alarm clock.
The "EEPROM_writeAnything" and "EEPROM_readAnything" functions will automatically jump from the first byte to the next and save or
read the entire number, even if the value if higher than 254. What you have to amke sure is to not overlap the EEPROM positions.
For example, if you store first the number 1200 on position 0, you have to know that the process will ocupy
positions 0, 1, 2, 3 and 4. So, the enxt number must be stored on position 5 higher.
Example:
Write function ....
The next line, will store the 1200 number on positions 0, 1, 2, 3 and 4 of the EEPROM automatically, since 1200 divided by 254 = 4,7(Bytes) so it will fit in 5 bytes.
EEPROM_writeAnything(0, 1200);
We know that bytes 0 to 5 are already ocupied by previous line so we write the second number starting from position 5. The function will
automatically fill bytes 5, 6, 7 and so on...
EEPROM_writeAnything(5, 2000);
Now we will di some reading ...
With the next line we read the number starting on position "0" and store into the my_value_1 variable. So, with the next line, my_value_1 will
be equal to 1200, which is the value we haev stored before.
EEPROM_readAnything(0, my_value_1);
With the next line we read the number starting on position "5" and store into the my_value_2 variable. So, with the next line, my_value_1 will
be equal to 2000, which is the value we haev stored before.
EEPROM_readAnything(5, my_value_2);
*/
if (millis()-last_writetime_long > 120000) //save to EEPROM every 2 minutes => 120000/1000millis=120 sec, 120/60=2min
{
variable.voltage = voltage_display;
variable.capacity = mah;
//EEPROM_writeAnything(0,variable);
last_writetime_long = millis();
}
} //end of main loop.
/*
Project description:
--------------------
1) Battery: 37 V, 5 Ah + switch + 30A flat fuse holder Battery connection: Since I don't like unnecessary cables, there is a SUB-D plug on the luggage rack and the charger a SUB-D socket on the battery, each with 20A high-current contacts from Reichelt ( http://www.reichelt.de/SUB-D-Mischk...E=12160&SHOW=1&START=0&OFFSET=16&;PROVID=2402 ) The small ones Pins in the plug are the balancer connection for the battery cells.
2) Throttle: With built-in potentiometer + brake contact socket.
The 6 original wires in the cable are sufficient for: ground, 5V, gas voltage, potentiometer voltage, switch, brake. This saves you an additional cable for the brake contact
3) Display: 2x8 characters from Reichelt ( http://www.reichelt.de/LCD-Module-D...E=44908&SHOW=1&START=0&OFFSET=16&;PROVID=2402 ), connected to the
Arduino via 8-pin black flat cable with RJ45 plug
4) Arduino NANO:
Battery current and voltage measurement: Attopilot Current and Voltage Breakout http://www.sparkfun.com/products/9028 , are two voltages proportional
to current and battery voltages out which are evaluated with the Arduino
5) Program:
- Regulates engine power depending on the potentiometer setting of 0-500 watts when pedaling (less than half a pedal turn until assistance starts / stops)
- starting aid up to 6 km / h
- If the throttle grip is pressed while pedaling, the power preselection on the potentiometer is ignored and the gas is given accordingly - good if you want
to get off the ground quickly - Undervoltage shutdown at 33V
- Calculates the capacity drawn from the battery -> Based on this, the percentage battery charge should be determined later . The determination
of the battery voltage as at the moment is a bit inaccurate ...
- Display: Display voltage, capacity in mAh and%, motor power
Still to do: - Connect the speed sensor (reed contact) to the Arduino
- Torque sensor? But I don't think I need it because the power control is very convenient
*/