DC motor speed controller with regen braking, using ATTINY85/45. - Page 1
Forum section: electronics / projects

author: Refrigerator
author_rank: Super Contributor
timestamp: October 26, 2021, 06:11:39 pm
content: 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 itAnd 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 VDS-ONof 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.Code:/* throttle and brake end point adjustmentworks 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 limitsuint8_t motor_PWM_max = 200; // max power limit to motor from 0 to 255uint8_t motor_PWM_limp = 100; // max pwm when in limp mode once battery voltage is too low, cannot be above max PWMuint8_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// variablesuint8_t throttle_val; // throttle value variable for adc readoutuint16_t battery_val; // battery value variable for adc readoutuint8_t PWM_to_throttle_register; // calculated PWM value to send to registeruint8_t PWM_to_brake_register; // calculated PWM value to send to registeruint16_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 haltedTCCR0B = 0b00000001; // set clock source for timerTCCR0A = 0b10100001; // enable OC0A1 and set waveform gen to slow pwmDDRB = 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 PB2ADCSRA =(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 iswhile (ADCH > throttle_min_val || ADCH < brake_min_val){ADCSRA |= (1 << ADSC); // start ADC measurementwhile (ADCSRA & (1 << ADSC) ); // wait til conversion complete}// this should prevent runaway to some extent}void loop() {// measure throttle ADC// set analog multiplexerADMUX &= ~( 1 << MUX1 ); // ADC 1 on PB2// measure voltageADCSRA |= (1 << ADSC); // start ADC measurementwhile (ADCSRA & (1 << ADSC) ); // wait til conversion completethrottle_val = ADCH; // read adc val//~~~~~~~~~~~~~~~~~~~~~~if (throttle_val > throttle_min_val){ // if throttle is appliedPWM_to_brake_register = 0; // brake PWM = 0if (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 appliedPWM_to_throttle_register = 0; // throttle PWM = 0if (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 multiplexerADMUX |= ( 1 << MUX1 ); // ADC 3 on PB3// measure the voltageADCSRA |= (1 << ADSC); // start ADC measurementwhile (ADCSRA & (1 << ADSC) ); // wait til conversion complete//battery_val = (ADCH << 2) | (ADCL >> 6); // read adc valbattery_val = (uint16_t)(ADCL >> 6) | (uint16_t)(ADCH << 2); // read adc val//adjust battery value according to load PWMbattery_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 registersOCR0A = PWM_to_brake_register;//OCR0B = PWM_to_throttle_register;//*// control throttle according to battery voltageif ( 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.

author: Siwastaja
author_rank: Super Contributor
timestamp: October 26, 2021, 06:19:02 pm
content: Regen comes automatically by just using half-bridge with two controllable switches, i.e., two MOSFETs instead of the simplest MOSFET+diode. As discussed in the other thread in the beginner section, diodes in parallel with MOSFETs are not usually needed.A gate driver capable of driving that high-side MOSFET is needed, a bootstrap IC is the cheapest and simplest solution and quite OK at that power level.Always keeping either of the MOSFETs on, i.e., switching between low and high and adjusting the duty cycle controls between drive / brake force. If you want to coast, you can carefully feed back the duty cycle so that current measures zero, or simply just disable both MOSFETs.And yes, the circuit is inherently equivalent to a synchronous buck, which is also equivalent to synchronous boost! The motor is equivalent to the L and C of the buck/boost, L being the winding inductance and C the inertia, voltage over C being the back-EMF voltage (voltage generated by the motor, linearly dependent on the RPM).When you do add the current sense (and I'd recommend: start here!), keep in mind it needs to be bidirectional. There are bidirectional current sense amplifiers which have a bias pin so that zero current equals some output voltage (such as Vcc/2).

author: Siwastaja
author_rank: Super Contributor
timestamp: October 26, 2021, 06:24:00 pm
content: ... and use a ground side shunt (i.e., from lower FET source to ground) instead of Vds sensing because it's easier to implement. I like your idea about a nail. You can calibrate it in software.An amplifier would be highly recommendable but maybe the nail can dissipate enough power to cause measurable voltage difference with ADC only. Try to come up with some kind of ghetto "Kelvin connection" though. Biasing for bidirectional sensing would require some trickery though.

author: Refrigerator
author_rank: Super Contributor
timestamp: October 26, 2021, 09:53:00 pm
content: Quote from: Siwastaja on October 26, 2021, 06:19:02 pmI considered the half bridge idea at first but was a bit afraid of accidentally turning on both MOSFETs at once, which would be pretty bad.IIRC TCC1 in Attiny85/45 can generate two phase correct PWM signals with deadtime so i might look more into that.As for the current measurement, i think i'll measure the low side current with a diff amp.My Vref right now is the internal 2.56V, which i chose for stability.But i might switch to the (potentially noisy) V+ as Vref. This way i can use V+/2 as a reference for a virtual ground for the diff amp.Then at startup i would read the current at 0 PWM and assume that this value is my zero point.Then i would only need to know the volt per amp ratio.With a 5V reference my ADC would have approx 5mV resolution and about 4V of usable range, before i get into any non-linearities in the ADC and the diff amp.And if i had, say 40mV/A, i could get about 0.125A resolution and a +-50A range.I could use a simple 7805 to regulate my V+ and voltage stability wouldn't even matter because if V+ drifts so does the virtual ground along with it.Looks like i might be whipping up a homemade PCB for this driver because this might be a little too complicated to build neatly on a perf-board.

author: Benta
author_rank: Super Contributor
timestamp: October 26, 2021, 10:18:57 pm
content: Somehow, the approach here seems to be backwards to me, but I'm probably just old fashioned.If I were to do a design like this, I'd start with the load: OK, predifined, it's a bike. Then I'd move on to the motor and it's specifications.This would in turn lead me on to the power stage needed for this motor. When this is specified, gate drivers defined, switching frequency for good regulation selected, power management and, and, and, THEN I'd look at which MCU might be suitable.Starting with "...using ATTINY85/45" is totally foreign to me. Sorry.

author: Refrigerator
author_rank: Super Contributor
timestamp: October 26, 2021, 10:59:08 pm
content: Quote from: Benta on October 26, 2021, 10:18:57 pmNo need to apologise, this project is not the kind where you could calculate anything.For starters, the DC motor is from the radiator cooling fan from my crashed SAAB - good luck finding the specs for that.It's got enough power to push me at 17kph with a 4S lithium pack, which is pretty good considering what this motor was made for.And even without a cooling fan the motor doesn't get too hot to touch.Also it's not like i have a range of micros to choose from, rather i chose what i have in my parts bin.The Attiny85/45 has enough PWM outs and analog ins to work, so why not?I don't think it's worth going into deep effort for a side project.Edit: Found out why my previous LM393 PWM controller blew, turns out my freewheeling diode cooked and went open, which then took out my switching FET.

author: Refrigerator
author_rank: Super Contributor
timestamp: October 27, 2021, 10:23:28 am
content: Checked out the PWM with dead time in timer 1 and it seems pretty simple and straight forward.But inverting one of the PWM outputs on timer 0 worked also.Problem is that timer 0 might not be glitchless, i'll have to look into it some more.I want to be able to control each output independently for coasting and pure regen when going downhill, for example.I haven't yet found how to do that with timer 1.

author: Refrigerator
author_rank: Super Contributor
timestamp: October 27, 2021, 12:32:50 pm
content: Added some stuff to the code, there's now adjustable dead time for PWM when applying throttle and both FET's should switch like a synchronous buck.When throttle is in the deadband the motor is coasting.Because of the inverted brake PWM the brake is now applied at 100% at startup if throttle is not in the deadband because of the runaway protection.I guess it's a feature now, right?No bugs hereStill a bunch of ideas tumbling around in my head.The new throttle switching scheme makes braking kind of redundant, but i still want my motor to be able to free wheel also.Problem is that (i think) now there will be a harsh transition between throttle and freewheeling while moving.Updated code below.Code:/* throttle and brake end point adjustmentworks 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 limitsuint8_t motor_PWM_max = 220; // max power limit to motor from 0 to 255uint8_t motor_PWM_limp = 100; // max pwm when in limp mode once battery voltage is too low, cannot be above max PWMuint8_t brake_PWM_max = 200; // max pwm to brake from 0 to 255#define PWM_dead_time 10 // dead time for PWM//note at 255 PWM brake there will be no regen// battery parameters#define battery_voltage_low 1//13.6 // 3.4V per cell on a 4S pack#define battery_voltage_cutoff 1//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// variablesuint8_t throttle_val; // throttle value variable for adc readoutuint16_t battery_val; // battery value variable for adc readoutuint8_t PWM_to_throttle_register; // calculated PWM value to send to registeruint8_t PWM_to_brake_register; // calculated PWM value to send to registeruint16_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 haltedTCCR0B = 0b00000001; // set clock source for timerTCCR0A = 0b11100001; // enable and set waveform gen to slow pwm// A output inverted and B output not inverted.DDRB = 0b00000011; // set PB0 and PB1 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 PB2ADCSRA =(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 iswhile (ADCH > throttle_min_val || ADCH < brake_min_val){ADCSRA |= (1 << ADSC); // start ADC measurementwhile (ADCSRA & (1 << ADSC) ); // wait til conversion complete}// this should prevent runaway to some extent}void loop() {// measure throttle ADC// set analog multiplexerADMUX &= ~( 1 << MUX1 ); // ADC 1 on PB2// measure voltageADCSRA |= (1 << ADSC); // start ADC measurementwhile (ADCSRA & (1 << ADSC) ); // wait til conversion completethrottle_val = ADCH; // read adc val//~~~~~~~~~~~~~~~~~~~~~~if (throttle_val > throttle_min_val){ // if throttle is appliedif (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;}if (255 - PWM_to_throttle_register > PWM_dead_time){PWM_to_brake_register = (~PWM_to_throttle_register) - PWM_dead_time;if (PWM_to_brake_register > brake_PWM_max) PWM_to_brake_register = brake_PWM_max;}else PWM_to_brake_register = 0;}//~~~~~~~~~~~~~~~~~~~~~~~else if (throttle_val < brake_min_val){ // if brake is appliedPWM_to_throttle_register = 0; // throttle PWM = 0if (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 multiplexerADMUX |= ( 1 << MUX1 ); // ADC 3 on PB3// measure the voltageADCSRA |= (1 << ADSC); // start ADC measurementwhile (ADCSRA & (1 << ADSC) ); // wait til conversion complete//battery_val = (ADCH << 2) | (ADCL >> 6); // read adc valbattery_val = (uint16_t)(ADCL >> 6) | (uint16_t)(ADCH << 2); // read adc val//adjust battery value according to load PWMbattery_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 registersOCR0A = ~PWM_to_brake_register;//OCR0B = PWM_to_throttle_register;//*// control throttle according to battery voltageif ( 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;}// */}

author: Siwastaja
author_rank: Super Contributor
timestamp: October 27, 2021, 12:42:07 pm
content: Quote from: Refrigerator on October 26, 2021, 09:53:00 pmThis is non-issue if you use a half-bridge gate driver, which is the easiest and simplest and lowest-cost solution anyway. You'd struggle to find one which allows you to do overlapped driving! Almost all implement deadtimes, some even have just "IN" and "EN" inputs which is easier to drive from a single PWM pin and such accident is impossible to happen. Others have separate IN_H and IN_L, but still implement protection against both being accidentally '1', so that valid combinations are 00, 01, and 10.QuoteIIRC it indeed can, but even if it couldn't, you could just use a half bridge driver with IN/EN inputs. IN driven by single PWM channel, EN with a GPIO.QuoteIt's the best way because choosing a suitable amp allows you to bias the output for bidirectional sensing.Although if you want to cheap-ass it, IIRC Tiny25/55/85 comes with a differential x20 gain ADC mode (unless I'm mixing it with something else in my head) and thiscouldwork with common mode of a few hundred mV below ground; being able to directly measure the shunt voltage. Worth testing if you want to reduce number of parts.

author: Refrigerator
author_rank: Super Contributor
timestamp: October 27, 2021, 05:49:50 pm
content: Quote from: Siwastaja on October 27, 2021, 12:42:07 pmI don't have any gate drivers on hand and i don't feel like ordering them and waiting for them to arrive.I think a simple gate driver with a couple NPN transistors should work well enough. At least my LTspice sim appears to say so.The Attiny has diff amps with adjustable gain but that would take two IO pins instead of just one.So an external diff amp is my only choice.I don't want to use the RESET pin as an IO because that would require a high voltage programmer, which i don't have.So in total i have 5 IO pins that i can use and right now all of them are taken up.PB0 - low side FET PWMPB1 - high side FET PWMPB2 - throttle signalPB3 - battery voltage measurementPB4 - (soon to be) current measurementIt is possible to use PB5 (reset) as an IO without needing a high voltage programmer. For example using it with an ADC and staying below the reset threshold.So in a pinch i can use it, but for now i think everything fits well enough.Included is a pic of my high side driver on LTspice. Not with the exact components but as a quick test works well enough and can be refined later.

author: magic
author_rank: Super Contributor
timestamp: October 27, 2021, 06:16:30 pm
content: I think you could get a half-bridge guaranteed to be crossconduction-free if you use complementary source followers for the switches.This needs low power boosted rails on both the negative and positive side, however.I see no reason to use Schottky for D1 in your circuit. Forward voltage is inconsequential but leakage is bad.

author: Siwastaja
author_rank: Super Contributor
timestamp: October 27, 2021, 06:40:22 pm
content: In any case, I don't find the cross conduction to be a huge risk item if you write the code to configure the timer with dead time generation, then verify with scope before soldering in the MOSFETs. That part of code is unlikely to suddenly break later.

author: Refrigerator
author_rank: Super Contributor
timestamp: October 27, 2021, 09:31:41 pm
content: Quote from: Siwastaja on October 27, 2021, 06:40:22 pmI've already implemented dead time generation with a parameter to set it in the code above.But i don't like that when transitioning from the dead band to throttle i'll be transitioning from 0% to brake_PWM_max in one step.Perhaps i should keep regen to a minimum when in the throttle range? Just enough to freewheel.But then the more load on the motor the more freewheeling there will be and i can't let that go through the body diode of the FET.I think i should build the thing so that i can test it out. After i figure out the current sensing fully, of course.Quote from: magic on October 27, 2021, 06:16:30 pmIt's just my habit to put Schottkeys everywhere when playing with LTspice. I assume a 1N4148 would work for this also.

author: Benta
author_rank: Super Contributor
timestamp: October 27, 2021, 10:12:50 pm
content: Quote from: Refrigerator on October 26, 2021, 10:59:08 pmSo it's like a "Zeppelin Bike"? Cool.

author: Refrigerator
author_rank: Super Contributor
timestamp: October 27, 2021, 10:43:14 pm
content: Quote from: Benta on October 27, 2021, 10:12:50 pmNot exactly what you would call a zeppelin, i'll include a pic.The fan was smashed so i just extracted the DC motor and strapped it to my old scooter that i got way back when i was about 5 years old.So this hot pink (BigClive approved, no doubt) scooter has been through hell and back and is likely on it's last journey before the inevitable scrap pile.

author: Siwastaja
author_rank: Super Contributor
timestamp: October 28, 2021, 08:45:28 am
content: Well for the simplest first iteration, just make the throttle command the duty cycle and use a half bridge. That way, the transition between drive / brake is completely smooth. The exact duty cycle number where the transition happens depends on the RPM, though.The next step would be to add current regulation loop, a simple PI loop works well and P, I tuning is noncritical as long as it's on the right ballpark. Input to the PI loop is the current measurement, output is duty cycle.This way you can make the throttle lever just setcurrent setpointwhich would a signed number, again completely smooth transition between drive / brake.Whichever you do, smooth the lever input for example by moving cumulative average so that you don't have sudden changes in setpoint.

author: Siwastaja
author_rank: Super Contributor
timestamp: October 28, 2021, 08:49:36 am
content: Quote from: Benta on October 26, 2021, 10:18:57 pmI understand that kind of approach and sometimes do it myself. For hobby projects, it's kind of obvious, but it's a valuable skill for professional life as well, now it's evidenced by the component crisis and needing to be able to work with whatever parts you can get.ATTiny45/85 specifically is a good example because IMHO, it's kind of the smallest microcontroller with the minimum amount of peripherals that can successfully implement a brushed DC motor controller. It has ADC, analog comparator and dual-channel PWM with deadtime generation, and enough CPU oomph and memory to run a few PI(D) loops and a simple UI. So, while quite small for the job, it's not an irrational choice either.If it was a BLDC controller, I would suggest some STM32F030 part as the minimum although there are parts in AVR series which can do that, but they are more expensive than the smallest ARM parts.

author: Refrigerator
author_rank: Super Contributor
timestamp: October 28, 2021, 02:48:50 pm
content: Quickly drew up a schematic. Might be the final version, might not.I have some IRFB4110 and IRF1324's. The IRF540's in the schematic are only placeholders.Same pinout, same TO-220 so who cares, right.To drive the gates i chose TO-92 BC547's because i have a bag of those.But i might swap them for some SMD 2N3904's just because drilling holes is tedious.R5 and R6 are very important, because a nasty side efect of this gate driver is that it's on by default.So if my micro pins are ever high Z the pullup resistors will keep the gates low.Might need to add pads for a programming header for the ATtiny also.Or just might make a clip to put on the IC instead, would be more versatile in the long run as i could use it on other DIP-8 IC's.Also i think one FET per side should be enough, at least it was enough for my previous driver.Since op-amps come in pairs most of the time i used the spare one to buffer the diff amp reference.And added a potentiometer instead of a set voltage divider in case my op-amp has significant offset, which would get amplified by the gain.Feel free to criticize.Edit:oops forgot to add the voltage divider for the battery voltage measurement.fixed

author: Refrigerator
author_rank: Super Contributor
timestamp: October 28, 2021, 06:49:43 pm
content: Oh and it looks like both of my gate drive signals need to be inverted because of the drivers.No biggie, just two bits in TCCR0A.

author: Refrigerator
author_rank: Super Contributor
timestamp: October 29, 2021, 06:00:13 pm
content: Turns out i had forgotten the pin header for the throttle potentiometer also, so i just bodged a trimmer in, same pitch anyways.Drew up a schematic, leaving some space to bend the MOSFETs over.Will probably mount THT parts on the surface because i don't feel like drilling holesDidn't bother cleaning the silkscreen up because it's a homebrew board anyways.Had some 50x70mm single sided FR4 substrate so that's how big i made the board.And will have to add two jumper wires because i couldn't make two last traces connect, not great, not terrible.

author: Refrigerator
author_rank: Super Contributor
timestamp: October 31, 2021, 03:28:31 pm
content: In my infinite wisdom i managed to etch the board mirrored.So component pins are mirrored also. No big deal for the transistors and FETs but pretty terrible for the ICs.To work around this i basically just bent the pins on the IC's over and soldered them upside down.Managed to find my USBAsp and tried programming my Attiny45 with it but got errors.A quick glance at the schematic reveals that i have capacitors on data pins - oops.Before making any changes to the board i pulled out another Attiny45 and tried programming it on the breadboard - no dice.I keep running into this error:Code:avrdude: warning: cannot set sck period. please check for usbasp firmware update.Pretty weird, because the Attiny's i'm trying to flash have already been flashed with this exact USBAsp many moons ago.Anyways, looks like i'll be butchering a Digispark board.Bummer.

author: Refrigerator
author_rank: Super Contributor
timestamp: November 01, 2021, 12:33:20 pm
content: Pulled the Attiny85 off my digispark board but it doesn't seem to like booting with all the auxillary stuff connected to it.I assume it's a problem with the bootloader.Regardless, i got the thing running.Next problem i noticed is the slow gate rise/fall times (~800 ns), which seems to have come as a result of the 47 Ohm gate resistors.I chose these resistors to keep the peak current down so that my BC547's don't pop.Playing around with a small motor on the desk it seems to work allright.But another problem i noticed is that my JRC4558 is acting weird.Adjusting the offset voltage down close to 0 the buffer amp shoots up to V+. Yet it works normally above 1-ish V.And also my diff amp seems to not work at all.I'll have to go to LTspie to check my sanity, because all connections seem correct.

author: magic
author_rank: Super Contributor
timestamp: November 01, 2021, 12:51:42 pm
content: 455x-456x stuff has phase reversal at the negative rail just like TL072 (and they are indeed almost identical).Replace with LM358 or add a negative supply.

author: Refrigerator
author_rank: Super Contributor
timestamp: November 01, 2021, 01:42:39 pm
content: Quote from: magic on November 01, 2021, 12:51:42 pmReplaced with a HA17358 and that fixed the problem.I had also mixed up a 20k and a 1k resistor so after swapping those around i finally get my current output.The signal looked noisy so i added an RC filter (6k8+10nF - arbitrary values), which seems to clean up the switching noise pretty well.The signal is still wavy because of the motor rotating and doing it's thing, but that's ok (i think).Current measurement both ways seems to work as intended.What you can see in the second screenshot is me turning the throttle potentiometer back and forth so when the motor decelerates it does a little bit of regen.

author: Siwastaja
author_rank: Super Contributor
timestamp: November 01, 2021, 02:45:41 pm
content: Can you synchronize ADC conversion to the same timer you use to generate PWM in ATTiny85? I don't remember. If you can, you can avoid switching noise by sampling at a convenient point.You can add some weight on the motor axle to get some momentum if you want to bench test the regen a bit better, although that trace already shows it seems to work.

author: Refrigerator
author_rank: Super Contributor
timestamp: November 01, 2021, 03:53:42 pm
content: Quote from: Siwastaja on November 01, 2021, 02:45:41 pmRight now i'm relying on my loop being fast enough but i'll have to get a timed interrupt going anyways for the PI loop so i might trigger the ADC off it also.But i think the measured result would be very dependent on the PWM anyways.Also, even though the thing works, the Digispark bootloader does get in the way. During startup it flashes the LED, but that pin is also connected to one of the gate drivers. Fortunately it only controls the FET responsible for braking.It's quite unfortunate that i can't get my USBAsp to work, perhaps i could get it to work with AtmelStudio. That's the way i did it all those years ago.Funny that reflashing the programmer firmware requires a working programmer.

author: Refrigerator
author_rank: Super Contributor
timestamp: November 02, 2021, 03:03:25 pm
content: I tried soldering the whole Digispark board in but quickly realized that it wouldn't be possible to do so.The circuitry used to interface to USB would interfere with my analog signals and voltage dividers.Reconfiguing pins would not have helped also.But i fould a way around the issue.I soldered the Attiny in and added 22k resistors separating PB4 and PB3 pins from the analog circuitry.Then i soldered pins to connect to the bare Digispark with jumper wires.This way i can use the bare Digispark board as a programming interface of sorts and it seems to work.

author: Refrigerator
author_rank: Super Contributor
timestamp: November 02, 2021, 05:02:40 pm
content: I had a roll of 1.5mm galvanized steel wire and 20mm of that comes out at about 1mOhm so i used it for the shunt.But with the small power supply and motor there wasn't enough current draw to see how well the shunt works.To test it i hooked up my smaller 4S5P lithium pack and a 60W 12V tungsten bulb.For whatever reason the MCU would keep browning out and i didn't know why so i kept resetting it and testing voltages on my scope.Until i saw that my batery voltage would drop at startup down to about 3V for 15-ish µs.I wanted to find out what was happening but before i could the thing exploded in the most spectacular fashion.A literal ball of flame engulfed the entire board and a massive ploom of smoke filled the room.It stinks so bad.Without a doubt that is because both FETs got turned on at once, unfortunately i don't know why.I might have to re-think my approach to this.

author: Siwastaja
author_rank: Super Contributor
timestamp: November 02, 2021, 05:30:39 pm
content: Add a fuse rated for that DC voltage to prevent that fireball the next time. The fuse won't save the FETs but it will save from other fire damage so you can just replace the FETs and the fuse.Cross-conduction isn't the only possible cause for MOSFET explosion. Overvoltage peak from bad layout is one, overcurrent detection circuit not reacting in time is another. Also too slow gate switching in either direction, or somehow the gate charging gets stuck midway.

author: Refrigerator
author_rank: Super Contributor
timestamp: November 02, 2021, 06:35:09 pm
content: Quote from: Siwastaja on November 02, 2021, 05:30:39 pmFound the problem and it's not in my board actually.At startup, when PB0 is high and PB1 is low my Attiny switches PB0 low for 15 µs and shorts out the FETs.My gate drivers invert so pin being low means gate is high.I tried writing to PORTB to drive both outputs high but they're at opposite states meaning that my timer 0 has overridden PORTB register.I might have to switch to timer 1 to see if it gets rid of this problem, it's supposed to be glitchless after all.The fuse is a good idea, i might have to add it, or just a thin wire considering how many times i might blow this thing up.

author: Refrigerator
author_rank: Super Contributor
timestamp: November 02, 2021, 06:49:29 pm
content: Still no idea what's going on but i think i'm getting closer to finding out.I made my PORTB and DDRB writes happen first thing in the code.I think the 15µs glitch comes from my timer being enabled but i have to dig deeper.

author: Siwastaja
author_rank: Super Contributor
timestamp: November 02, 2021, 07:18:14 pm
content: Inverted gate drivers are PITA, you might want to add simple pull-up resistors and keep the DDR register set in input mode until you are "ready to go".

author: Refrigerator
author_rank: Super Contributor
timestamp: November 02, 2021, 07:23:58 pm
content: I think i found out what's going on.Seems like when you enable aninvertedoutput in PWM mode it stays low for15 µsactually 6 µs now.Shuffling some lines of code around made it so that at no point are two putputs low.But the156 µs bug still remains and i don't think i can get around it.Regardless, the signals no longer overlap.

author: Refrigerator
author_rank: Super Contributor
timestamp: November 02, 2021, 07:27:15 pm
content: Quote from: Siwastaja on November 02, 2021, 07:18:14 pmI did actually. I mentioned this in post #17.Normally i would go for a non-inverting driver because it's inherently safer since it doesn't drive the gate high when no input is applied.But when playing around in LTspice i couldn't get it fast enough so i caved in and went with an inverting driver.The difference between the two is that one uses NPN transistors, while the other uses PNP.

author: Refrigerator
author_rank: Super Contributor
timestamp: November 02, 2021, 10:52:33 pm
content: Man this ESC is just a gift that keeps on giving.Also i think the Digispark bootloader is tripping me up as well.With light loads the thing seems to work fine but when i tried to connect the 60W bulb it refuses to work.As soon as any current starts going the MCU shuts down and i've yet fo find out what's causing it.In the process of trying to find out i wanted to measure the 5V rail to see if it's stable and just putting my scope probe there increased stability to a point where i was able to light up the bulb.But most of the time the bulb would either not light up or just flash briefly.I had made a simple fusible link with a cutoff resistor leg as the fuse material, this way i can just replace it once it breaks.And it has broken once already yet my FETs seem unaffected so it's pretty good at protecting i guess.But perhaps it was the more than 5A flowing through that poor thing when my bulb lit up.The wire didn't break while the bulb was on actually but when i was trying to get it going.This process of trying included me rapidly turning the throttle pot back and forth like a DJ in hopes of getting the bulb to light up, mainly to see if my current shunt is working.And it's really weird how with larger loads the PWM just dies but the MCU doesn't seem to reset, otherwise it would go through the whole LED flashing routine.Oh and i haven't coded in any current limit yet so that can't be what's shutting the thing down.

author: Refrigerator
author_rank: Super Contributor
timestamp: November 06, 2021, 06:40:28 pm
content: Brought my spare arduino nano that i had left in my uni dorm and used it as an ICSP on the tiny85.And it worked first try, and there's no longer that annoying bootloaderBut the problem, where PWM disappears at high peak currents, remains and is annoying as ever.And for whatever reason i can't find what's causing it.fixed*Weirdly enough when i placed my scope probe on PB2 (throttle input) things got stable enough to light the bulb.I took the opportunity to see what kind of current measurement i get and it came out at about 67.7mV/A.I measured the current with my DMM in series with the bulb but it wasn't at 100% PWM so it might not be completely accurate.But i noticed that with the DMM in series the bulb is easier to light up yet again.* Basically i chopped up all the grounds and wired everything so that there's now a star ground and replaced the 20k resistors i had previously to allow programming with 750R, which i think should still allow me to program the Tiny85 through ICSP. And now the thing starts up reliably.Ps: now thinking about it, for the current measurement, i could probably just have shorted the low side FET with my DMM and could have measured the current and voltage that way.

author: Refrigerator
author_rank: Super Contributor
timestamp: November 06, 2021, 07:22:56 pm
content: Since the bulb only draws a measly ~3.3A (even though it's supposed to be 60W at 12V) i need to find some bigger load.But with the added stability i was finally able to see what peak current the bulb draws.Only 42AProbably more because it looks like my current signal is clipped, yikes.

author: Siwastaja
author_rank: Super Contributor
timestamp: November 06, 2021, 07:57:10 pm
content: Yes, peak current of low voltage halogen bulbs can be massive, it's like a short circuit. You can easily blow up things assuming it has more resistance than it actually does.But there is not much inductance in the bulb, and such converter (with active current limiting, once you get it done) needs some inductance to limit current rise rate. In that sense, motor is easier as it has a lot of inductance, so current limit circuit can be quite slow (react in tens of microseconds).

author: Refrigerator
author_rank: Super Contributor
timestamp: November 14, 2021, 09:03:48 pm
content: With just a few lines of code i got the entire control loop timed from the timer0 overflow interrupt but not at full speed.I think my control loop is currently limited by the ADC speed, which i could still increase if i need to.Right now i have the loop running at approximately 100Hz, which i think should be fast enough, right?As @Siwastaja suggested i might change the control method for the throttle.Instead of controlling the PWM the potentiometer would control the current, this way i could control the regen and the throttle without any sharp transitions.But this will require some tuning to make the throttle scale appropriately.For example, let's say my motor only draws max 30A but i have the max throttle mapped for 60A, which would mean that i would be going at 100% power at only 50% throttle.I might try both PWM and current control.

author: Refrigerator
author_rank: Super Contributor
timestamp: November 14, 2021, 10:54:50 pm
content: Actually the new control method needs a bunch of code to be changed and i kind of want to at least test the controller out with simple current limit before i go with any big changes.I think i'll just make PWM drop to 0 whenever it's over set current for starters.That's the simplest current control i can think of.Edit: oops turns out my timed interrupts are not working. I thought they were because my LED was changing brightness but i had connected it wrong.

author: Refrigerator
author_rank: Super Contributor
timestamp: November 15, 2021, 12:29:35 am
content: Turns out whatever library i'm using for my Attiny85 is bugged.Selecting the board as Digispark allows me to compile the code so i assume it's good.But i can't upload the code anymore if i compile it with the Digispark board selected.Guess i'm off to the datasheet to find where TIMER0 overflow interrupt vector is located.Actually, since i'm no longer using the MCU as a Digispark board i should probably just jump over to Atmel studio and try my luck thereProblem code:Code:ISR(TIMER0_OVF_vect){Systick ++;Sampling_tick ++;}And the error i get:Code:wiring.c.o (symbol from plugin): In function `__vector_5':(.text+0x0): multiple definition of `__vector_5'sketch\digispark_esc_with_regen_ver_1.2.ino.cpp.o (symbol from plugin):(.text+0x0): first defined herecollect2.exe: error: ld returned 1 exit statusexit status 1Error compiling for board ATtiny25/45/85.

author: Refrigerator
author_rank: Super Contributor
timestamp: November 15, 2021, 12:45:16 pm
content: Reverted the code to freerun again and added current control.The PWM now drops to 0 whenever overcurrent is detected.I knew this would not be perfect but it has created the most amusing side effect.This current control method makes my motor emit sounds resembling that of a robot crying out in pain.Has my spaghetti code become sentient? Have i accidentally created a motor controller that feels pain?

author: Zero999
author_rank: Super Contributor
timestamp: November 15, 2021, 01:58:20 pm
content: Quote from: Refrigerator on October 27, 2021, 10:43:14 pmAre you sure it can take your weight and where's the brake? It looks like a fun project, but it's also an accident waiting to happen.

author: Refrigerator
author_rank: Super Contributor
timestamp: November 15, 2021, 05:13:30 pm
content: The current control loop is very knee-jerk-ish and seems to be hard to control.When testing with a light bulb i ramp up the PWM slowly and when current limit kicks in the brightness drops, which i assume is instability in the control loop.Might also be about time to re-write my code in Atmel studio because Arduino IDE ain't it, chief.

author: Refrigerator
author_rank: Super Contributor
timestamp: November 15, 2021, 05:17:15 pm
content: Quote from: Zero999 on November 15, 2021, 01:58:20 pmThe entire point of the controller i'm building is the regen brake.

author: Zero999
author_rank: Super Contributor
timestamp: November 15, 2021, 06:15:59 pm
content: Quote from: Refrigerator on November 15, 2021, 05:17:15 pmBut it shouldn't be the only brake.There should be a real brake, as a backup.

author: Refrigerator
author_rank: Super Contributor
timestamp: November 15, 2021, 06:42:50 pm
content: Quote from: Zero999 on November 15, 2021, 06:15:59 pmTrue, but i didn't have any hardware to add brakes to this scooter so the next best thing was to add regen.The scooter will not be driven on the road/traffic or any dense area so i think it's fine.It's a toy anyways and 17kph is not even that fast.

author: Zero999
author_rank: Super Contributor
timestamp: November 15, 2021, 07:15:55 pm
content: Quote from: Refrigerator on November 15, 2021, 06:42:50 pmYou don't have to go that fast to hurt someone. Cool. I'm not meaning to be discouraging. I just get concerned because where I live, there's a problem with silly people on electric scooters. If you ride it where you can't run anyone over, then fine.Quote from: Refrigerator on November 15, 2021, 05:13:30 pmIs there a time delay for the overcurrent protection? The motor, wiring and transistors should be able to withstand short surges. Perhaps adding an RC circuit to the current sense would help?

author: Refrigerator
author_rank: Super Contributor
timestamp: November 15, 2021, 08:13:59 pm
content: Quote from: Zero999 on November 15, 2021, 07:15:55 pmThe e-scooter kids in bigger cities are quite troublesome, i'm quite aware of this.As well as the blinding headlights some of them have, and god the ones where they flash.But i digress...There's already an RC circuit in the current control loop because the signal coming from the opamp would be quite noisy because of the motor comutation.Since the loop is free running it will grab the ADC value with each loop, so as fast as it can basically.The way my current limit works is that if the current measured it above the set limit then it will overwrite the throttle potentiometer reading from the ADC and park it in the middle of the deadband.Less code to rewrite this way.I might add another cap to the RC filter on the output of the diff amp so that it filters the signal even more.I don't want to add too much of a delay though.Also i'm still trying to figure out how to get the timed interrupts going.Including:Code:#include <avr/interrupt.h>Made the errors go away but the interrupt still does not work.I compared my code to some other tutorials online and it seems to be correct.I enable global interrupts with sei(), i enable timer0 interrupt with TIMSK|=(1<<TOIE0); but my loop does not work indicating that my tick variables are not incrementing.So i might try running the interrupt on timer1.

author: Refrigerator
author_rank: Super Contributor
timestamp: November 15, 2021, 09:38:47 pm
content: Made a progarm copy where only timer0 is used but in the same phase correct PWM mode.I've found out that the line:Code:TIMSK|=(1<<TOIE0); //enabling timer0 interruptStops my code from running. Commenting it out makes my code work again.A real head scratcher this one.

author: Refrigerator
author_rank: Super Contributor
timestamp: November 15, 2021, 10:03:37 pm
content: For whatever reason enabling timer0 overflow interrupt in phase correct PWM mode is no bueno.I've looked through the datasheet multiple times now and can't find any hints as to why.Starting to get tired of these issues to be honest.Here's the code i'm testing stuff out with:Code:#include <avr/interrupt.h>int Sampling_tick, Sampling_period;ISR(TIM0_TOV0_vect){//ISR(TIMER0_COMPA_vect){OCR0B = 255;Sampling_tick ++;}void setup() {//TIMSK|=(1<<TOIE0); //enabling timer0 interruptPORTB = 0b00000011;DDRB = 0b00000011; // set PB0 and PB1 as output.OCR0A = 0;OCR0B = 10;TCCR0B = 0b00000001; // set clock source for timerTCCR0A = 0b10100001; // enable and set waveform gen to slow pwm// enable global interrupt//sei();/* enable timer 0 interrupt on counter overflow */TIMSK|=(1<<TOIE0); //enabling timer0 interrupt/* global interrupt enable *///sei();SREG |= 1 << 7;}void loop() {//delay(10);//OCR0A ++;//OCR0B ++;if (Sampling_tick >= Sampling_period){// nothing happens here yet}}//ISR(TIM0_TOV0_vect){////ISR(TIMER0_COMPA_vect){// OCR0B = 255;// Sampling_tick ++;//}OCR0B = 10;works but no otherOCR0B = whatever;work after timer0 interrupt enable bit is set in TIMSK.Ps: i'm using a spare Digispark board to test this code out because it's easier. Also i know interrupts (in general) on timer0 work because i have used them previously but i have no idea why they aren't working now.

author: Refrigerator
author_rank: Super Contributor
timestamp: November 16, 2021, 10:18:41 pm
content: Okay i've changed the current control yet again.Now the ADC reading from the throttle potentiometer is just a target and if current is below the treshold i increment the control value by 1 until it either reaches max current or reaches the throttle ADC value.If current is above or the control value is above the throttle ADC reading then i decrement the control value by 1 each loop.I should probably make the decrement value a seperate thing to perhaps change the response of the loop.I increment the value in each loop cycle so it more or less ramps steadily.I haven't yet been successful at getting the interrupt to run and i've given up on interrupts for now.Also i added some code that reads the base current whenever the throttle potentiometer is set in the deadband to compensate for drift in diff amp reference voltage.Tested the new control algorithm out on the scooter and it's much quieter than previous one.Weird thing i noticed is that the current limit works but i think it's being affected by the noise somehow?With the current limit set at 10A when i throttle up with the motor stalled it sits at 3A but if i let the motor run it jumps to 9A~10A.If i stall the motor it settles down at 3A again.This is the current coming from the battery.Measuring the current in the motor loop at stall it's around 10A and drops when i let the motor run.