Iniciación al control de motores brushless (BLDC)

Los motores brushless (sin escobillas) se caracterizan precisamente por eso, por no tener ningún elemento que provoque rozamiento entre el rotor y la carcasa exterior.
Son muy usados en muchas aplicaciones, pero las que nos pillan más de cerca seguramente sean los discos duros y los ventiladores de los PC: la mayoría de ellos están gobernados con motores brushless.

En este artículo empezaremos desde cero hasta conseguir hacer girar un motor NIDEC 44F5098005 extraído de una vieja unidad de almacenamiento en cinta.


Un poco de teoría

Para hacer que giren se requiere de una conmutación electrónica en sus bobinados que genere un campo magnético perpendicular a la dirección del rotor.
Normalmente estos motores tienen tres fases, separadas 120º:

Y se consigue la rotación excitando las bobinas que correspondan en cada momento en función de la orientación del motor.
Para saber cuál es la posición del rotor en cada momento se utilizan dos técnicas básicamente, dependiendo de la existencia o no de sensores en el motor, lo que los divide en dos familias:

• Sensored: disponen de sensores hall o encoders que indican la posición del rotor. Es habitual que tengan 3 sensores separados 120º, al igual que ocurre con los bobinados.
• Sensorless: no tienen sensores; la posición se determina mediante un cálculo sobre el comportamiento de la corriente en el motor. Quedan fuera del alcance de este artículo.

Las técnicas de control de los motores con sensores se clasifican según el algoritmo de control utilizado. Los más usados son estos, en orden creciente de eficiencia y complejidad:

• Conmutación trapezoidal o 6 steps mode
• Conmutación sinusoidal
• Control vectorial o Field Oriented Control

La más simple es la conmutación trapezoidal, que será la que utilizaremos en este artículo.
En la siguiente animación se puede observar cómo se excitan las distintas bobinas con el polo positivo (recuadro rojo) o el negativo (recuadro azul) para generar un campo magnético en una dirección o en la opuesta y provocar una rotación del rotor, que está polarizado magnéticamente.


Hemos representado los polos magnéticos también con colores rojo y azul. Sabiendo que los polos del mismo color se repelen, y los de distinto color se atraen, se puede entender porqué el rotor se posiciona en cada uno de los 6 ángulos distintos que conforma el giro.

Utilizando esta técnica siempre tendremos una única bobina alimentada. Podemos realizar una combinación nueva aprovechando para alimentar dos bobinas simultáneamente en los momentos que correspondan. De esta manera tendremos un giro de 12 pasos en vez de 6. Lógicamente el consumo del motor aumenta y el torque también. En esta animación se observa el funcionamiento:

Conectamos el motor


Nuestro motor tiene un conector con 9 pines, de los cuales utilizaremos estos 8:
• Extremo A del bobinado
• Extremo B del bobinado
• Extremo C del bobinado
• Vdd
• GND
• Sensor Hall 1
• Sensor Hall 2
• Sensor Hall 3

Podemos comprobar al alimentar un par de extremos del bobinado que el motor se alinea en uno de los 6 puntos de la estrella que forman las distintas combinaciones que podemos hacer con ellas (AB, BC, CA, BA, CB, AC):

En nuestro caso no tenemos datasheet del motor, así que lo primero que vamos a hacer es averiguar cuál es la lectura de los sensores para cada una de las 6 combinaciones mencionadas anteriormente.
Para ello vamos a utilizar un PIC 18F8722, fácilmente sustituible por cualquier otro, en el que tenemos conectado los tres sensores en los pines menos significativos del puerto J. Este PIC está conectado mediante un MAX233 al puerto serie del PC lo que nos permite utilizar fácilmente un Terminal serie para ver los valores que deseemos.
El programa que vamos a usar es muy simple: un bucle infinito que envía continuamente al Terminal el valor del puerto J del PIC:

// lectura sensores hall
#bit  S1             = PORTJ.0
#bit  S2             = PORTJ.1
#bit  S3             = PORTJ.2
void main()
{
set_tris_d(0xc0);
set_tris_J(0x0F);
PORTJ=0;
PORTD=Libre;
delay_ms(10);
printf ("Arranque:nr");

while (1) {
printf (%u%u%u “,S3,S2,S1);
}
}

En una primera prueba, no energizamos ningún bobinado, y giramos el motor manualmente. Observamos que la secuencia de valores obtenida es la siguiente:
001 -> 011 -> 010 -> 110 -> 100 -> 101

Obviamente, si giramos el eje en sentido contrario:
101-> 100 -> 110 -> 010 -> 011 -> 001

Lo que buscamos, es saber qué bobinado debemos alimentar en cada una de las seis posiciones en función de que deseemos girar el motor hacia un lado o hacia el otro. Por tanto, lo primero que haremos será alimentar el motor con las 6 combinaciones descritas y anotar el valor de la lectura de los sensores.

El resultado es:
AC lleva a 010
BC lleva a 110
BA lleva a 100
CA lleva a 101
CB lleva a 001
AB lleva a 011

Una vez que hemos deducido cuál es la correspondencia entre bobinas y sensores, podemos hacer girar el motor excitando la combinación de bobinas que correspondan en un sentido o en otro según la lectura de los encoders en cada momento.

Tras hacer distintas pruebas este es el resultado de combinaciones con nuestro motor:

Orden de ejecución                1   2   3   4   5   6
Combinación                      110 010 011 001 101 100
A la izquierda                    AB  CB  CA  BA  BC  AC
A la derecha                      BA  BC  AC  AB  CB  CA

Con estas combinaciones conseguimos un giro de 7500 revoluciones por minuto y bastante torque a baja velocidad.

También podemos hacerlo girar con esta otra combinación:

Orden de ejecución                1   2   3   4   5   6
Combinación                      110 010 011 001 101 100
CB  CA  BA  BC  AC  AB
CA  BA  BC  AC  AB  CB

Con esta nueva combinación conseguimos que el motor vuele a 10200 rpm pero pierde bastante torque a bajas revoluciones.

De hecho, creo que una buena solución sería un sistema dinámico que en función de la velocidad utilizase una u otra combinación para exprimir el máximo torque y la máxima velocidad.

El programa

En nuestro ejemplo utilizamos la primera combinación metiéndola en un array bidimensional:

int8 SiguienteCombinacion[2][7]={
0, BA, CB, CA, AC, BC, AB,   // Izda
0, AB, BC, AC, CA, CB, BA};   // Derecha

El array tiene sus valores ordenados según el valor de la lectura de los sensores Hall.  Así, si por ejemplo leemos un valor 101 en ellos, representará un índice 5 en el array. En función del sentido de giro que estemos gestionando en ese momento, el índice 5 nos extrae los valores BC o CB del array, que son los que corresponden a la primera combinación que mostramos anteriormente.

El siguiente programa reúne todo lo que hemos comentado, y funciona de la siguiente manera: al enviar una tecla ‘0’ vía terminal serie el motor se conecta y comienza a girar en un sentido. Si se vuelve a enviar se para. Si la tecla es ‘1’ hace lo mismo pero en sentido contrario.

Mientras el motor esté girando, en el terminal va apareciendo su velocidad de giro y podemos comprobar cómo responden las RPM cuando lo frenamos con la mano.

#include <18F8722.h>
#device adc=8
#FUSES NOWDT                    //No Watch Dog Timer
#FUSES WDT128                   //Watch Dog Timer uses 1:128 Postscale
#FUSES HS                       //High speed Osc (> 4mhz)
#FUSES NOPROTECT                //Code not protected from reading
#FUSES IESO                     //Internal External Switch Over mode enabled
#FUSES NOBROWNOUT               //No brownout reset
#FUSES BORV25                   //Brownout reset at 2.5V
#FUSES NOPUT                    //No Power Up Timer
#FUSES NOCPD                    //No EE protection
#FUSES STVREN                   //Stack full/underflow will cause reset
#FUSES NODEBUG                  //No Debug mode for ICD
#FUSES NOLVP                      //Low Voltage Programming on B3(PIC16) or B5(PIC18)
#FUSES NOWRT                    //Program memory not write protected
#FUSES NOCPB                    //No Boot Block code protection
#FUSES NOEBTRB                  //Boot block not protected from table reads
#FUSES NOEBTR                   //Memory not protected from table reads
#FUSES NOWRTD                   //Data EEPROM not write protected
#FUSES NOWRTC                   //configuration not registers write protected
#FUSES NOWRTB                   //Boot block not write protected
#FUSES FCMEN                    //Fail-safe clock monitor enabled
#FUSES LPT1OSC                  //Timer1 configured for low-power operation
#FUSES MCLR                     //Master Clear pin enabled
#FUSES XINST                    //Extended set extension Indexed Address mode enabled
#FUSES MCU                      //Microcontroller Mode

#use delay(clock=10000000)
#use rs232(baud=19200,parity=N,xmit=PIN_C3,rcv=PIN_C4,bits=8,restart_wdt,FORCE_SW)

// c3 emite, c4 recibe
#byte PORTD             = 0xF83
#byte PORTJ             = 0xF88

// Salidas al motor
// positivas
#bit  P1             = PORTD.3 // NEGRO    0
#bit  P2             = PORTD.4 // BLANCO   0
#bit  P3             = PORTD.5 // AMARILLO 1
// negativas
#bit  N1             = PORTD.0 // AMARILLO 0
#bit  N2             = PORTD.1 // AZUL     1
#bit  N3             = PORTD.2 // GRIS     1

// lectura sensores hall
#bit  S1             = PORTJ.0
#bit  S2             = PORTJ.1
#bit  S3             = PORTJ.2

//#bit  sentido        = PORTD.7
// bits del PORTD: P3 P2 P1 N3 N2 N1
#define A_ON   P1=0;N1=0   // 000000 = 0
#define B_ON   P2=0;N2=0   // 000000 = 0
#define C_ON   P3=0;N3=0   // 000000 = 0
#define A_OFF  P1=1;N1=1   // 001001 = 9
#define B_OFF  P2=1;N2=1   // 010010 = 18
#define C_OFF  P3=1;N3=1   // 100100 = 36
#define A_FLO  P1=1;N1=0   // 001000 = 8
#define B_FLO  P2=1;N2=0   // 010000 = 16
#define C_FLO  P3=1;N3=0   // 100000 = 32
#define  AC    52 // {A_ON;B_FLO;C_OFF;} = 16+36=52
#define  BC    44 // {A_FLO;B_ON;C_OFF;} = 36+8=44
#define  BA    41 // {A_OFF;B_ON;C_FLO;} = 9+32=41
#define  CA    25 // {A_OFF;B_FLO;C_ON;} = 9+16=25
#define  CB    26 // {A_FLO;B_OFF;C_ON;} = 18+8=26
#define  AB    50 // {A_ON;B_OFF;C_FLO;} = 18+32=50
#define  Libre 56 // {A_FLO;B_FLO;C_FLO;} = 8+16+32=56
/*
A_ON;B_FLO;C_OFF;  AC lleva a 010 -> PORTD=52, PORTJ=2
A_FLO;B_ON;C_OFF;  BC lleva a 110 -> PORTD=44, PORTJ=6
A_OFF;B_ON;C_FLO;  BA lleva a 100 -> PORTD=41, PORTJ=4
A_OFF;B_FLO;C_ON;  CA lleva a 101 -> PORTD=25, PORTJ=5
A_FLO;B_OFF;C_ON;  CB lleva a 001 -> PORTD=26, PORTJ=1
A_ON;B_OFF;C_FLO;  AB lleva a 011 -> PORTD=50, PORTJ=3

Orden de ejecución                1   2   3   4   5   6
Combinación                      110 010 011 001 101 100
A la izquierda                    AB  CB  CA  BA  BC  AC // 7500 RPM y mucho torque
A la derecha                      BA  BC  AC  AB  CB  CA // 7500 RPM y mucho torque

// con estas combinaciones vuela a 10200 rpm
// pero pierde torque a bajas revoluciones
CB  CA  BA  BC  AC  AB
CA  BA  BC  AC  AB  CB

Orden de ejecución                4   2   3   6   5   1
Combinación                      001 010 011 100 101 110
*/


int8 SiguienteCombinacion[2][7]={
0, BA, CB, CA, AC, BC, AB,   // Izda
0, AB, BC, AC, CA, CB, BA};   // Derecha
char c;
char buffer[50];   // en este buffer se almacenan los comandos recibidos
int IndBuffer=0;
int HayComando=0;  // flag que determina si comando pendiente de procesar
int1 rotacion=0;
int8 sentido=0;
int8 ticks=5;
int8 antLectura;
int16 pap;
char CargaEnBuffer(char c) {
buffer[IndBuffer++]=c;
if (c==13)
HayComando=1;
return(c);
}

// En función del primer dígito (Buffer[0]) se actúa en consecuencia
void ProcesaComando() {
if ((buffer[0]=='0') || (buffer[0]=='1')) {
rotacion=!rotacion;
PORTD=Libre;
sentido=buffer[0]-'0';
}

IndBuffer=0;   // Volvemos a iniciar el buffer
HayComando=0;  // Ya se ha procesado el comando
}

void RecepcionSerie2() {
CargaEnBuffer(fgetc());
}
#int_timer1
void CalculaRPM() {
set_timer1(3033);
ticks--;
if (!ticks) {
ticks=5;
printf ("%Lu, ",pap*15/6);
pap=0;
}
}

void main()
{
int retraso;
set_tris_d(0xc0);
set_tris_J(0x0F);
PORTJ=0;
PORTD=Libre;
delay_ms(10);
retraso=1000;
printf ("Arranque:nr");
setup_timer_1(T1_INTERNAL | T1_DIV_BY_8);
enable_interrupts(INT_TIMER1);
enable_interrupts(GLOBAL);

while (1) {

if (kbhit())
RecepcionSerie2();
if (HayComando)
ProcesaComando();
if (rotacion)
PORTD=SiguienteCombinacion[sentido][PORTJ & 0x7];

if (antLectura!=(PORTJ & 7)) {
antLectura=PORTJ & 7;
pap++;
}

}
}

El circuito

El circuito que hemos usado para controlar el motor es un conjunto de tres pares de transistores, que nos permiten polarizar a 0V o 12V cada uno de los extremos de las tres bobinas del motor.

Hemos colocado un 7406 previamente como buffer de salida del PIC y para adaptar los niveles de tensión ya que estamos usando salidas que no son Schmith Trigger. También hemos colocado una resistencia shunt de 0,1 ohmios que nos permitirá hacer una vigilancia del consumo del motor desde una entrada analógica del PIC. Pero eso escapa del alcance de nuestro artículo de hoy, que finaliza aquí mismo.

Espero que sea de utilidad.

Share