BLDC Motor Controller Using Arduino

I wrote the below Arduino uno code to control a sensored bldc motor using the attached schematic diagram circuit.
The controller can run the motor in the tow rotation directions(cw & ccw) and the speed can be controlled using a potentiometer connected to A0. In the code there are tow functions fwd() and bwd() each function is for the rotation direction selected by switches on pins 10 & 11, the switch on pin 12 is to stop the motor.
Project Blog: http://elecnote.blogspot.com

#include <PWM.h>
int32_t frequency = 20000; //pwm frequency in Hz
unsigned int n = 0, timer2_initial_value, s = 0;
void setup(){
  InitTimersSafe();
  bool success = SetPinFrequencySafe(9, frequency);
  pinMode(3, OUTPUT);
  pinMode(4, OUTPUT);
  pinMode(5, OUTPUT);
  pinMode(6, OUTPUT);
  pinMode(7, OUTPUT);
  pinMode(8, OUTPUT);
  pinMode(13, OUTPUT);
  pinMode(0, INPUT);
  pinMode(1, INPUT);
  pinMode(2, INPUT);
  pinMode(10, INPUT_PULLUP);
  pinMode(11, INPUT_PULLUP);
  pinMode(12, INPUT_PULLUP);
  // initialize timer2 interrupt for adc reading 
  noInterrupts();           // disable all interrupts
  TCCR2A = 0;
  TCCR2B = 0;
  timer2_initial_value = 0;  
  TCNT2 = timer2_initial_value;   // preload timer
  TCCR2B |= (1 << CS22) |(1 << CS21) | (1 << CS20); // 1024 prescaler 
  TIMSK2 |= (1 << TOIE2);   // enable timer overflow interrupt
  interrupts();             // enable all interrupts
}
 ISR(TIMER2_OVF_vect)        // interrupt service routine 
 {
  TCNT2 = timer2_initial_value;   // preload timer
  n++;
  if (n>20){
    n = 0;
   if (s != analogRead(A0)){
   s = analogRead(A0); 
  pwmWrite(9,s/4);}}
 }
int fwd(){
   while(1){
if (digitalRead(2)==1){
    if (digitalRead(1)==0){
      if (digitalRead(0)==1){
        digitalWrite(8,0);digitalWrite(7,0);digitalWrite(6,0);
        digitalWrite(5,1);digitalWrite(4,1);digitalWrite(3,0);}
      else {
        digitalWrite(8,1);digitalWrite(7,0);digitalWrite(6,0);
        digitalWrite(5,1);digitalWrite(4,0);digitalWrite(3,0);}}
        
     if (digitalRead(1)==1){
      if (digitalRead(0)==0){
        digitalWrite(8,1);digitalWrite(7,0);digitalWrite(6,0);
        digitalWrite(5,0);digitalWrite(4,0);digitalWrite(3,1);}}}
        
   
 if (digitalRead(2)==0){
    if (digitalRead(1)==1){
      if (digitalRead(0)==0){
        digitalWrite(8,0);digitalWrite(7,0);digitalWrite(6,1);
        digitalWrite(5,0);digitalWrite(4,0);digitalWrite(3,1);}
      else {
        digitalWrite(8,0);digitalWrite(7,1);digitalWrite(6,1);
        digitalWrite(5,0);digitalWrite(4,0);digitalWrite(3,0);}}
        
     if (digitalRead(1)==0){
      if (digitalRead(0)==1){
        digitalWrite(8,0);digitalWrite(7,1);digitalWrite(6,0);
        digitalWrite(5,0);digitalWrite(4,1);digitalWrite(3,0);}}}
   if (digitalRead(12)==0) break;}
}
 int bwd(){
   while(1){
if (digitalRead(2)==1){
    if (digitalRead(1)==0){
      if (digitalRead(0)==1){
        digitalWrite(8,0);digitalWrite(7,0);digitalWrite(6,1);
        digitalWrite(5,0);digitalWrite(4,0);digitalWrite(3,1);}
      else {
        digitalWrite(8,0);digitalWrite(7,1);digitalWrite(6,1);
        digitalWrite(5,0);digitalWrite(4,0);digitalWrite(3,0);}}
        
     if (digitalRead(1)==1){
      if (digitalRead(0)==0){
        digitalWrite(8,0);digitalWrite(7,1);digitalWrite(6,0);
        digitalWrite(5,0);digitalWrite(4,1);digitalWrite(3,0);}}}
        
   
 if (digitalRead(2)==0){
    if (digitalRead(1)==1){
      if (digitalRead(0)==0){
        digitalWrite(8,0);digitalWrite(7,0);digitalWrite(6,0);
        digitalWrite(5,1);digitalWrite(4,1);digitalWrite(3,0);}
      else {
        digitalWrite(8,1);digitalWrite(7,0);digitalWrite(6,0);
        digitalWrite(5,1);digitalWrite(4,0);digitalWrite(3,0);}}
        
     if (digitalRead(1)==0){
      if (digitalRead(0)==1){
        digitalWrite(8,1);digitalWrite(7,0);digitalWrite(6,0);
        digitalWrite(5,0);digitalWrite(4,0);digitalWrite(3,1);}}}
   if (digitalRead(12)==0) break;}
}
     
void loop(){
  digitalWrite(13, LOW);
  pwmWrite(9,s);
  digitalWrite(8,0);digitalWrite(7,0);digitalWrite(6,0);
  digitalWrite(5,0);digitalWrite(4,0);digitalWrite(3,0);
  if (digitalRead(10)==0) {digitalWrite(13, HIGH);bwd();}
  if (digitalRead(11)==0) {digitalWrite(13, HIGH);fwd();}
}

This video shows the working:

Since D0 and D1 are the Serial pins on an Arduino UNO it is best to leave them unused if possible. I would move the buttons to A1-A3 (which can be used as digital pins) and slide the other connections up two pins. This will allow Serial.print() to be used for debugging.

Yes it is better to let pins 0 & 1 unused and since pin9 is for pwm signal I can change the pins 0,1,2 to A1,A2,A3 that pins are feedback and comes from hall sensors.
Thanks
johnwasser

Those unwieldy fwd() bwd() functions need some attention... Table look-up is the
way to perform this sort of thing - combine the Hall inputs into a 3-bit number,
use that to index an array to return a 6-bit output, which is then split out to the
outputs.

Most 3-phase bridges don't have a separate PWM input, this is best done by
encoding the PWM into the lookup-table and using a pin-change interrupt to drive the outputs, so 4 bits to the table-lookup (3 hall bits and PWM state). Thus a pin-change interrupt
on pin 9 would be needed. To keep timings tight direct-port manipulation is
usually required (write all 6 outputs in one instruction, rather than 25us for
6 calls to digitalWrite).

MarkT:
Those unwieldy fwd() bwd() functions need some attention... Table look-up is the
way to perform this sort of thing - combine the Hall inputs into a 3-bit number,
use that to index an array to return a 6-bit output, which is then split out to the
outputs.

Most 3-phase bridges don't have a separate PWM input, this is best done by
encoding the PWM into the lookup-table and using a pin-change interrupt to drive the outputs, so 4 bits to the table-lookup (3 hall bits and PWM state). Thus a pin-change interrupt
on pin 9 would be needed. To keep timings tight direct-port manipulation is
usually required (write all 6 outputs in one instruction, rather than 25us for
6 calls to digitalWrite).

Thanks mr MarkT
For the pwm inside the bridge is as shown in the attached picture.
How can I write all the 6 outputs in just one instruction?

pwm.png

spokba:
Thanks mr MarkT
For the pwm inside the bridge is as shown in the attached picture.
How can I write all the 6 outputs in just one instruction?

i interested to your logic construction. Its nice to use only 1 PWM pin, And execuse me Sir, i want to ask, so the 6 output from the AND logic straight go to N channel mosfet?

To write pins simultaneously, make sure they are all on the same "port" from the AVR chip.

Arduino Pin Map

See the pin names next to the chip? "PD2" "PD3"? That says those pins are in port D. You can write a byte directly to the port to change all 8 pins at exactly the same instant. With a bit of care, you can do this for 6 pins in a way that doesn't disturb the other pins on the port, such as the serial pins on PD0 and PD1.

digitalWrite() is kind of slow but it is portable between different Arduinos. Hacking the ports directly is not portable, although equivalents exist on all Arduinos, so just make sure you write the code with pin and port definitions at the top so you only need to change it in one place in your code.

The bridge circuit is shown on the schematic below:
More details about this project are on:

http://elecnote.blogspot.com

spokba:
Thanks mr MarkT
For the pwm inside the bridge is as shown in the attached picture.
How can I write all the 6 outputs in just one instruction?

That circuit with 6 and gates is clearly flawed - you don't PWM every pin in the same
direction simultaneously!!!

I said use the PWM pin to generate a pin change interrupt, and do the work
in the pin-change interrupt routine.

[ BTW If you know you'll only be PWMing the high side devices, say, then you
could use a 3-and-gate version of that circuit ]

MarkT:
That circuit with 6 and gates is clearly flawed - you don't PWM every pin in the same
direction simultaneously!!!

I said use the PWM pin to generate a pin change interrupt, and do the work
in the pin-change interrupt routine.

[ BTW If you know you'll only be PWMing the high side devices, say, then you
could use a 3-and-gate version of that circuit ]

It is better to have six pwm outputs, and ATmega328 can do that if we use all the 3 timers, we may need at least 1 timer which means we can not generate 6 pwm signals. So we have to generate just one pwm signal and route this signal to the active switches with the help of the AND gates, each gate controls one switch.

Note that you can only use independent (unsynchronized) PWM outputs if only PWMing
one device at a time, which pretty much forces trapezoidal drive and PWMing only 3
devices in total (all the highs or all the lows).

It is possible to synchronise all 3 timers with care (set them all in an equivalent mode,
disable interrupts and write the count registers with experimentally derived values, re-enble
interrupts). Then you can have 6 synchronised PWM signals (allowing sinusoidal drive
for instance, or fast-decay mode for trapezoidal).

If it's not already too late, may I offer an Arduino with the 32U4 on it (leonardo, etc.) might be better suited to what you want to accomplish.

I think you'll find the timers are designed to do 3-phase control and I remember there being an app note on the Atmel site for doing this. As long as (as MarkT suggested) you will be doing direct register access, do it on a CPU with a couple more bells and whistles.

The leonardo seems to have 3 PWM driven by timer1 and 3 PWM driven by timer4, which
might be a bit more logical for 3phase, but the pin out is pretty random and the two sets
clash...

For this project it is easier to work with microchip PIC18FXX31 series it has 6 pwm outputs and with MikroC compiler you just write pwmx_start(); where x defines the output number and goes from 1 to 6.

I've generated trapezoidal signals before with one PWM pin and no external hardware -
I just use the PWM pin to trigger a pin-change interrupt and then drive the bridge from
the ISR - works fine!

With synchronized AVR timers I've generated sinusoidal 3-phase PWM - use one of the timer's
overflow ISRs to update the 3 OCR registers by copying from global variables - again
does the job, drives induction motor nicely :slight_smile:

If it is a pure sinusoidal and has 120° between phases I think it will run it very well. Mr MarkT do you have any idea about controlling sensorless bldc motor and zero detection technique?

I built the same controller but with microchip PIC16F877A.

good morning ! we need to make a motor control 3 phase, AC, 10HP, batery bank is 96VDC, a connected potentiometer at Arduino, send signal to the 6 (six) outputs, the pulses have to be phased to excite the drivers, and then the signs go to the 6 IGBT, and then to the motor 3 phase AC motor, in the shaft is installed a "bicycle brake disc" with the holes measured by a TCST2103, this signal TCST goes to the arduino to make better the funcional set, I need help to function, it is to be used in large electric car, ok

So you need 3-phase bridge, current sensors on two of the phases (the third would be redundant of
course), encoder to measure shaft angle (preferably absolute), and a lot of experience with this
sort of motor control.

I think what you call TCST is an optical encoder?

This is a big project and with those power levels safety and probably legal compliance are your prime
concerns from the beginning.

Start small.

BTW you have an AC servo motor there, if using sinusoidal drive, a BLDC is basically the same
hardware with trapezoidal drive rather than sinusoidal, and hall-sensors for commutation.

I note you've started a new thread on this (this thread wasn't the right place for a whole
new project), here:
https://forum.arduino.cc/index.php?topic=385999.0