Crescer (fundo transparente)_Negativo_pn
  • Thales Gonçalves Ferreira

Controlando motor de passo com ESP32 e interrupção

Atualizado: 22 de jul.

Controlar motores de passo é uma tarefa que à primeira vista é bem simples, principalmente utilizando o driver TB6600. Porém, a necessidade de ficar constantemente dando pulsos no motor é um problema, ainda mais quando o microcontrolador está realizando muitas tarefas ao mesmo tempo, de modo que até controlar dois motores com velocidades diferentes pode ser um problema.

Isso ocorre quando controlamos o motor utilizando delay ou qualquer através de outras funções que travam o código durante a execução. Para resolver isso podemos utilizar o timer e a sua interrupção, assim o controle de tempo do acionamento das bobinas do motor é feito pelo hardware do microcontrolador, não gastando processamento para isso.


No blog de hoje vamos ver como controlar vários motores de passo ao mesmo tempo, com velocidades e sentidos de rotação diferentes, utilizando o driver TB6600 e a placa CPB 32.


Avalie-nos no Google para que possamos alcançar e auxiliar cada vez mais pessoas a fazerem seus projetos e produtos! Contamos com você!




Sumário


1. Driver TB6600

2. Interrupções

3. Interrupção do Timer

4. Controle dos motores



1. Driver TB6600


O driver é feito para controlar um motor de passo bipolar, 4 fios, possui proteção contra sobretensão, sobrecorrente e curto-circuito. Pode ser configurado para operar em 8 valores diferentes de corrente e também 5 tipos de passo, como podemos ver na imagem a seguir:


As configurações de passo e corrente podem ser feitas utilizando as chaves que estão na parte superior do driver, como podemos ver na figura a seguir. A posição das chaves está descrita na tabela impressa no driver, sendo as 3 primeiras chaves para controlar o tipo de passo utilizado e as outras para a corrente.


Os pinos Vcc e GND são utilizados para alimentar o drive, a tensão utilizada deve ser entre 9 e 42V, não é necessário utilizar a mesma tensão dos motores, inclusive é recomendado colocar tensão superior à nominal do motor.


O motor será conectado nos pinos A+, A-, B+ e B-, tomando cuidado para sempre identificar cada bobina, uma deve ser conectada em A e a outra em B.


Os pinos de controle, signal, são utilizados pelo microcontrolador para controlar o motor, os negativos, ENA-, DIR- e PUL- podem ser conectados diretamente ao GND, enquanto os positivos vão no microcontrolador.


O ENA+ é utilizado para habilitar o motor, se aplicamos nível baixo nele as bobinas serão energizadas, pode ser usado para travar o motor em uma posição, se aplicamos nível alto ele desliga as bobinas.


O DIR+ controla o sentido de rotação do motor, em nível alto o motor gira em um sentido, em nível baixo gira no sentido contrário. Em PUL+ serão aplicados os pulsos para fazer o motor girar, cada borda de descida aplicada fará o motor se deslocar um passo no sentido definida pelo pino DIR+, a frequência dos pulsos aplicados nesse pino define a velocidade de rotação do motor, quanto maior a frequência maior será a velocidade.


A Figura a seguir mostra as ligações do circuito básico para controlar 1 motor:



Para controlar o motor precisamos colocar o pino ENA+ em nível baixo, DIR+ em alto ou baixo e aplicar bordas de descida em PUL+, veja a seguir um programa exemplo:


#define en  15
#define dir 2
#define pul 5

void loop() {
 digitalWrite(en, LOW);
 digitalWrite(dir, HIGH);

 for (int x = 0; x < 500; x++) {
    digitalWrite(pul,LOW);
    delay(5);
    digitalWrite(pul,HIGH);
    delay(5);
 }
}

No código, o “for” foi utilizado para fazer o motor deslocar-se quinhentos passos. A velocidade do motor é definida pelos delays, aumentando o tempo reduzindo a velocidade do motor.



2. Interrupções


De uma forma geral, interrupção em um microcontrolador é um mecanismo que interrompe o código principal, fazendo o programa executar um outro conjunto específicos de instruções, uma função, que quando finalizada retorna a execução do código principal onde parou.

A questão principal a ser entendida é, o que dispara uma interrupção? Esse gatilho pode ter diversas fontes, como por exemplo a interrupção externa, que é disparada quando o microcontrolador recebe um estímulo em um de seus pinos, podendo ser por nível, alto ou baixo, mas também pode ser disparada por borda, subida ou descida.


Um exemplo de aplicação da interrupção externa seria utilizá-la para monitorar um sensor de fim de curso importante conectado no pino de interrupção. O programa pode ficar realizando outras coisas, sem precisar se preocupar com o estado do sensor, porém, no momento que ele for acionado a interrupção será disparada. Fazendo o código principal parar e dar total atenção ao efeito desse sensor.

Para esse projeto será utilizada a interrupção do timer. O timer é um circuito contados presente em praticamente todos os microcontroladores, por sem um dispositivo implementado hardware, o contador opera contando em paralelo com o código, portanto, independente do que está sendo executado pelo programa o contador estará trabalhando em segundo plano. A interrupção do timer dispara quando o contador terminar sua contagem, com isso podemos ter uma interrupção que é executada com um intervalo de tempo definido.



3. Interrupção do Timer


Como falado no item anterior, a interrupção do timer nos permite executar uma função específica com um período específico de tempo. Isso é muito útil para temporizar os pulsos de controle dos motores de passo, assim podemos acioná-los sem utilizar delay no código, pois o período de acionamento será controlado pelo timer e executado pela interrupção.

O código a seguir utiliza a interrupção do timer para incrementar uma variável a cada 1 segundo, muito similar a função millis, porém, com 1 segundo de período. Perceba que a função loop está totalmente vazia.



hw_timer_t * timer = NULL;

void setup() {
 Serial.begin(115200);

 timer = timerBegin(0, 80, true);

 timerAttachInterrupt(timer, &cb_timer, true);

 timerAlarmWrite(timer, 1000000, true);

 timerAlarmEnable(timer);
}

void loop() {

}

void cb_timer() {
 static unsigned int contador = 0;
 contador++;

 Serial.print("contador: ");
 Serial.print(contador);
 Serial.print("  Millis/1000: ");
 Serial.println(millis() / 1000);
}

Agora vamos analisar esse código para entender melhor como utilizar a interrupção.


hw_timer_t * timer = NULL;

Cria um ponteiro para a variável, do tipo hw_timer_t, que será usada para configurar o timer.


 timer = timerBegin(0, 80, true);

Essa função recebe três parâmetros de entrada: o número do timer que será utilizado, a ESP32 possui 4 trimers, de 0 a 3, o prescaler e o flag para indicar o sentido da contagem, crescente (true) e decrescente (false).


O prescaler é um divisor de frequência que é aplicado do clock antes do timer, ou seja, ele divide a frequência que será aplicada do contador. Como o clock da ESP32 é de 80MHz. Utilizando o prescaler em 80 estamos dividindo o clock por 80, portanto, aplicando 1MHz de clock no contador, dessa forma ele será incrementado a cada 1 microsegundo.


timerAttachInterrupt(timer, &cb_timer, true);

Essa função é responsável por vincular uma função (segundo parâmetro) com um timer (primeiro parâmetro), desta forma a função vinculada, cd_timer, será executada sempre que a interrupção foi gerada. O terceiro parâmetro define que a interrupção será por borda (true) ou nível (false).


timerAlarmWrite(timer, 1000000, true);

Agora definimos o período de execução da interrupção, o primeiro parâmetro é o timer que estamos configurando. O segundo será o valor que o contador do timer deve atingir para disparar a interrupção, como estamos com o prescaler em 80, contador está contando com período de 1 microssegundo, podemos definir o período exato, em microssegundos, que a interrupção será executada.

Nesse exemplo queremos que a interrupção seja disparada a cada 1 segundo, portanto, o parâmetro deve ser 1000000. O último parâmetro define que o contador será disparado novamente ao final da contagem.


timerAlarmEnable(timer);

Por fim, a última função de configuração é responsável por habilitar o timer para contar.


Dentro da função cb_timer estamos somente incrementando a variável contador e enviando seu valor pela serial junto com o valor da função millis, assim podemos utilizar o monitor serial para confirmar que a interrupção está realmente sendo executada com período de 1 segundo.


É importante destacar que o código dessa função deve ser executado o mais rápido possível, pois não pode ter um tempo de execução maior que o período da interrupção. Ou seja, não podemos executar uma função que leva 2 segundos para ser feita com um período de 1 segundo.



4. Controle dos motores


O objetivo aqui é controlar o motor utilizando a interrupção do timer, portanto, os pulsos de acionamento do motor serão dados dentro da interrupção, enquanto o controle de sentido e velocidade será feito no loop.

O programa foi estruturado de modo que consiga controlar uma quantidade N de motores, para isso a estrutura dos seus parâmetros e definições de pinos será feita com vetores, no código a seguir temos a declaração das variáveis com os pinos de controle dos motores e outras variáveis e definições importantes.


#define motores 2
#define tempo_int 1000 // período de interrupção em microsegundos

//Definição dos pinos do motor
int pul[motores] = {13, 27};
int en[motores] = {15, 16};
int dir[motores] = {2, 17};

//Declarando variáveis
bool pul_flag[motores] = {0, 0};
int velocidade[motores] = {0, 0};

int pot[motores] = {36, 39}; //pinos analógicos

Os vetores pul, en e dir são usados para informar para o programa os pinos da ESP que estão conectados ao driver, cada posição do vetor é referente a um motor, ou seja, se queremos adicionar mais motores precisamos somente adicionar os pinos nos vetores.

pul_flag é a flag utilizada para fazer a inversão do pino pul, utilizado na interrupção. O vetor velocidade, como o nome sugere, armazena o valor da velocidade do motor, lembrando que, ele controla o período de oscilação do pino pul, portanto, quanto maior o valor dessa variável menor será a velocidade de rotação do motor.


A velocidade e o sentido de rotação dos motores serão controlador utilizando potenciômetros, um para cada motor. quando o potenciômetro estiver no meio o motor estará parado, no máximo o motor gira com velocidade máxima em uma direção, no mínimo em velocidade máxima na outra direção. O vetor pot armazena as entradas analógicas para controlar os motores.


Os define motores e tempo_int são usados para definir, respectivamente, o número de motores que serão controlador e o período da interrupção do timer. Se o valor da velocidade estiver em 0 e o motor não estiver rodando com velocidade máxima significa que, o período da interrupção está muito grande, portanto, deve-se reduzir o valor de tempo_int, assim aumentamos a velocidade máxima do motor.


void setup() {
 for (int x = 0; x < motores; x++) {
    pinMode(pul[x], OUTPUT);
    pinMode(en[x], OUTPUT);
    pinMode(dir[x], OUTPUT);
    digitalWrite(en[x], HIGH);
 }

 timer = timerBegin(0, 80, true);

 timerAttachInterrupt(timer, &cb_timer, true);

 timerAlarmWrite(timer, tempo_int , true);

 timerAlarmEnable(timer);
}

No setup temos a justificativa para utilizar vetores nas definições dos pinos, utilizando um for podemos controlar os motores com base no valor de define motores, pois, o for percorre os vetores controlando ou ajustando um motor de cada vez, porém, com velocidade alta, de motor que para a nossa percepção os comandos acontecerão ao mesmo tempo.


Essa abordagem de utilizar for é utilizada em outros pontos do código, aqui ela foi responsável por definir os pinos como saída e desligar os motores. Depois disso temos a configuração do timer e da sua interrupção.


void cb_timer() {

  static unsigned int contador[motores] = {1 1};

  for (int x = 0; x < motores; x++) {
    contador[x]++;

    if (contador[x] >= velocidade[x]) {
      pul_flag[x] = !pul_flag[x];
      digitalWrite(pul[x], pul_flag[x]);
      contador[x] = 0;
    }
  }
}

Essa é a função utilizada na interrupção do timer, o vetor contador terá uma posição para cada motor, o for, da mesma forma que no setup, será responsável por controlar cada motor, incrementando contador, caso seu valor seja maior que a variável velocidade, o programa irá aplicar o pulso no motor. Contador é incrementado a cada tempo_int (em microsegundos), a variável velocidade controla o período de oscilação do pino pul.


#define ad_min 1800
#define ad_max 2300

#define vel_max 3
#define vel_min 50
            

void loop() {

 int motor_control[motores];

 for (int x = 0; x < motores; x++) {
    motor_control[x] = analogRead(pot[x]);

    if (motor_control[x] > ad_min && motor_control[x] < ad_max) {
      digitalWrite(en[x], HIGH);
    }
    if (motor_control[x] >= ad_max) {
      digitalWrite(en[x], LOW);
      digitalWrite(dir[x], HIGH);
      velocidade[x] = map(motor_control[x], ad_max, 4090, vel_min, vel_max);
    }
    if (motor_control[x] <= ad_min) {
      digitalWrite(en[x], LOW);
      digitalWrite(dir[x], LOW);
      velocidade[x] = map(motor_control[x], ad_min, 0, vel_min, vel_max);
    }
 }
}

Primeiro vamos entender os define que estão antes do loop, eles definem as constantes de velocidade máxima, mínima e os limites do potenciômetro. Novamente estamos utilizando um for para controlar cada motor individualmente, independentemente da quantidade de motores.

Se o potenciômetro estiver entre os limites, ad_min e ad_max, na posição central, o motor será desabilitado, aplicando nível lógico alto no pino en.


Se for maior que ad_max o motor será habilitado, colocando nível baixo no pino en, ajusta o sentido de rotação, nível alto em dir e ajusta a velocidade do motor utilizando a função map.


Com esse programa conseguimos controlar, de forma independente, cada um dos motores. podemos facilmente modificar o código para ter controle também do deslocamento, apresentando um vetor para contar o número de passos dados pelo motor durante a interrupção.


Gostou do conteúdo? tenta fazer esse exemplo aí, quando conseguir compartilha nas redes sociais e marca a gente @crescer_automacao!


Autor: Thales Ferreira


ANEXO - Código exemplo

hw_timer_t * timer = NULL;

/*Para Adicionar mais motores, altere a definição motores
   para o número de motores desejado, depois adicione
   os pinos de controle nos vetores a baixo.

   Para alterar a velocidade mude o valor do vetor velocidade,
   quando menor seu valor, maior a velocidade, a interrupção
   é disparaa a cada 1000us, se necessário aumentar a velocidade do motor,
   acima de 0 (zero) a definição tempo_int deve ser alterada, para reduzir
   o período da interrupção.
*/

#define motores 2
#define tempo_int 1000 // período de interrupção em microsegundos

//Definição dos pinos do motor
int pul[motores] = {13, 27};
int en[motores] = {15, 16};
int dir[motores] = {2, 17};

//Declarando variáveis
bool pul_flag[motores] = {0, 0};
int velocidade[motores] = {0, 0};

int pot[motores] = {36, 39}; //pinos analógicos

#define ad_min 1800
#define ad_max 2300

#define vel_max 3
#define vel_min 50

void cb_timer() {
  static unsigned int contador[motores] = {1};

  for (int x = 0; x < motores; x++) {
    contador[x]++;

    if (contador[x] >= velocidade[x]) {
      pul_flag[x] = !pul_flag[x];
      digitalWrite(pul[x], pul_flag[x]);
      contador[x] = 0;
    }
  }
}
void setup() {
  for (int x = 0; x < motores; x++) {
    pinMode(pul[x], OUTPUT);
    pinMode(en[x], OUTPUT);
    pinMode(dir[x], OUTPUT);
    digitalWrite(en[x], HIGH);
  }

  Serial.begin(115200);
  //startTimer();

  //inicialização do timer. Parametros:
  /* 0 - seleção do timer a ser usado, de 0 a 3.
    80 - prescaler. O clock principal do ESP32 é 80MHz. Dividimos por 80 para ter 1us por tick.
    true - true para contador progressivo, false para regressivo
  */
  timer = timerBegin(0, 80, true);
  /*conecta à interrupção do timer
    - timer é a instância do hw_timer
    - endereço da função a ser chamada pelo timer
    - edge=true gera uma interrupção
  */
  timerAttachInterrupt(timer, &cb_timer, true);
  /* - o timer instanciado no inicio
     - o valor em us para 1s
     - auto-reload. true para repetir o alarme
  */
  timerAlarmWrite(timer, tempo_int, true);
  //ativa o alarme
  timerAlarmEnable(timer);
}

void loop() {

  int motor_control[motores];

  for (int x = 0; x < motores; x++) {
    motor_control[x] = analogRead(pot[x]);
    Serial.println(motor_control[x]);

    if (motor_control[x] > ad_min && motor_control[x] < ad_max) {
      digitalWrite(en[x], HIGH);
    }
    if (motor_control[x] >= ad_max) {
      digitalWrite(en[x], LOW);
      digitalWrite(dir[x], HIGH);
      velocidade[x] = map(motor_control[x], ad_max, 4090, vel_min, vel_max);
    }
    if (motor_control[x] <= ad_min) {
      digitalWrite(en[x], LOW);
      digitalWrite(dir[x], LOW);
      velocidade[x] = map(motor_control[x], ad_min, 0, vel_min, vel_max);
    }
  }
}

Autor: Thales Ferreira

183 visualizações

Posts recentes

Ver tudo