Decided to post my ongoing project here. It's not a big project but a fun one, i think.
I have this homemade e-scooter that has gone through various changes and upgrades.
Honestly it's a pile of junk (quite literally) but fun to play around with (especially for my younger brother).
I had made a simple PWM controller for the motor with an LM393, which worked pretty well. Until i popped it
And before that i had just an on off switch that would either give power to the motor or short it out to brake.
I really liked the electric brake but there was no easy way to add it to the LM393 PWM controller so i decided to make a new one.
This one will be controlled by an Attiny85/45. I'm currently testing the code on a Digispark board and Aruino IDE because it's more convenient.
Once it's all done i'll flash the code to a spare Attiny45 using my USBAsp. But right now the code is still a bit of a mess.
Currently in my code i have:
*Throttle end point edjustment
*Brake end point adjustment (same pot as throttle)
*Dead band adjustment between throttle and brake.
*Throttle max PWM adjustment
*Brake max PWM adjustment
*Limp mode PWM adjustment (when battery is low)
*Battery voltage low treshold
*Battery voltage cutoff treshold
*Battery voltage sag compensation (because voltage sags under load)
*Battery voltage divider ratio (for the resistor divider)
*Runaway protection (for when you connect the battery)
Have yet to add to the code:
*Current measurement
*Current limit
*Hysteresis for state changes (cutoff <-> limp <-> full power)
Tell me if i missed some important feature.
The regen will be done quite crudely with a MOSFET in parallel to the freewheeling diode and a diode in parallel to the drive MOSFET.
Basically a boost converter in parallel to the driver.
I wanted to do the current measurement without a shunt by basically measuring the V
DS-ON of the drive MOSFET but my PWM frequency is at 33kHz so my µcontroller would be too slow i think. 33kHz because i wanted no PWM whine.
So i think i'll just use a galvanized nail as a shunt (galvanized because they solder easier).
When stalled the motor draws about 60-ish amps, so i need a pretty good shunt, hence the nail.
And no i'm not buying a shunt, that would take too long and cost too much.
/* throttle and brake end point adjustment
works pretty much the same as on an RC car
|<------------------------- ADC value from throttle potentiometer ---------------------->|
|<------- throttle range --------->|<- dead band ->|<------ brake range for regen ------>|
| | | |
| | | |
| | | |
throttle_max_val throttle_min_val brake_min_val brake_max_val
*/
#define ADC_reference_voltage 2.56 // volts
/*~~~~ Start of user parameters ~~~~*/
// throttle values
#define throttle_max_val 200 // expected throttle value max
#define throttle_min_val 100 // expected throttle value min
//Brake values
#define brake_min_val 80 // expected brake value min
#define brake_max_val 50 // expected brake value max
// power limits
uint8_t motor_PWM_max = 200; // max power limit to motor from 0 to 255
uint8_t motor_PWM_limp = 100; // max pwm when in limp mode once battery voltage is too low, cannot be above max PWM
uint8_t brake_PWM_max = 255; // max pwm to brake from 0 to 255
//note at 255 PWM brake there will be no regen
// battery parameters
#define battery_voltage_low 13.6 // 3.4V per cell on a 4S pack
#define battery_voltage_cutoff 12.5 // 3.125V per cell
#define battery_voltage_sag 10 // % of battery voltage sag at 100% throttle
#define battery_voltage_divider_ratio 10 // battery voltage is divided for ADC by x value
/*~~~~ end of user parameters ~~~~*/
// other stuff
#define throttle_range (throttle_max_val - throttle_min_val) // throttle range calculated
#define brake_range (brake_min_val - brake_max_val) // min and max reversed fromthrottle because math
// ^^^ brackets are for math reasons
// variables
uint8_t throttle_val; // throttle value variable for adc readout
uint16_t battery_val; // battery value variable for adc readout
uint8_t PWM_to_throttle_register; // calculated PWM value to send to register
uint8_t PWM_to_brake_register; // calculated PWM value to send to register
uint16_t battery_voltage_ADC_range ; //
//uint8_t runaway_protection_flag; // self explanatory (and also not needed)
void setup() {
GTCCR = 0b100000000; // timer setup register 1 = timer halted
TCCR0B = 0b00000001; // set clock source for timer
TCCR0A = 0b10100001; // enable OC0A1 and set waveform gen to slow pwm
DDRB = 0b00000011; // set PB0 as output.
OCR0A = 0;
OCR0B = 0;
ADMUX =
(1 << ADLAR) | // ADC works as 8-bit
(1 << REFS1) | // Set vref to 2.56V
(1 << REFS2) | // ^^^^^^^^^^^^^^^^^^
(0 << MUX3) | //
(0 << MUX2) | //
(0 << MUX1) | //
(1 << MUX0); // ADC 1 on PB2
ADCSRA =
(1 << ADEN) | // Enable ADC
(1 << ADPS2) | // set prescaler to 64, bit 2
(1 << ADPS1) | // set prescaler to 64, bit 1
(0 << ADPS0); // set prescaler to 64, bit 0
// if throttle is not in the deadband wait until it is
while (ADCH > throttle_min_val || ADCH < brake_min_val){
ADCSRA |= (1 << ADSC); // start ADC measurement
while (ADCSRA & (1 << ADSC) ); // wait til conversion complete
}
// this should prevent runaway to some extent
}
void loop() {
// measure throttle ADC
// set analog multiplexer
ADMUX &= ~( 1 << MUX1 ); // ADC 1 on PB2
// measure voltage
ADCSRA |= (1 << ADSC); // start ADC measurement
while (ADCSRA & (1 << ADSC) ); // wait til conversion complete
throttle_val = ADCH; // read adc val
//~~~~~~~~~~~~~~~~~~~~~~
if (throttle_val > throttle_min_val){ // if throttle is applied
PWM_to_brake_register = 0; // brake PWM = 0
if (throttle_val < throttle_max_val){
PWM_to_throttle_register = ((uint16_t)(throttle_val - throttle_min_val)* motor_PWM_max) / throttle_range; // scale value
}
else PWM_to_throttle_register = motor_PWM_max;
}
//~~~~~~~~~~~~~~~~~~~~~~~
else if (throttle_val < brake_min_val){ // if brake is applied
PWM_to_throttle_register = 0; // throttle PWM = 0
if (throttle_val > brake_max_val){
PWM_to_brake_register = ((uint16_t)(brake_min_val - throttle_val) * brake_PWM_max) / brake_range ; // scale value
}
else PWM_to_brake_register = brake_PWM_max;
}
else {
PWM_to_brake_register = 0;
PWM_to_throttle_register = 0;
}
//* // <- to comment out battery voltage measurement
// measure battery voltage
// set analog multiplexer
ADMUX |= ( 1 << MUX1 ); // ADC 3 on PB3
// measure the voltage
ADCSRA |= (1 << ADSC); // start ADC measurement
while (ADCSRA & (1 << ADSC) ); // wait til conversion complete
//battery_val = (ADCH << 2) | (ADCL >> 6); // read adc val
battery_val = (uint16_t)(ADCL >> 6) | (uint16_t)(ADCH << 2); // read adc val
//adjust battery value according to load PWM
battery_val += (((battery_val * battery_voltage_sag) / 100) * PWM_to_throttle_register ) / motor_PWM_max ;
if (battery_val > 1023) battery_val = 1023;
//*/
// transfer values to PWM registers
OCR0A = PWM_to_brake_register;
//OCR0B = PWM_to_throttle_register;
//*
// control throttle according to battery voltage
if ( battery_val <= (uint32_t)(1023 * battery_voltage_cutoff) / (ADC_reference_voltage*battery_voltage_divider_ratio)){
OCR0B = 0; // if battery too low no throttle
}
else {
if ( battery_val <= (uint32_t)(1023 * battery_voltage_low) / (ADC_reference_voltage*battery_voltage_divider_ratio)){
if (PWM_to_throttle_register > motor_PWM_limp) OCR0B = motor_PWM_limp; // if battery too low no throttle
}
else OCR0B = PWM_to_throttle_register;
}
// */
}
Ps: now thinking about it i could totally build a driver with all these features without any microntrocontroller. Might be a project for another time.