quarta-feira, 25 de junho de 2014

Controlando 3 motores de passo de forma simultânea com o Atmega328P.

Projeto de controle de 3 motores de passos. 

         Olá, hoje vou demonstrar como controlar 3 motores de forma simultânea e ao mesmo tempo você pode mudar o comportamento de cada um de forma independentes,para isto criei um aplicativo serial para monitorar e testar o funcionamento deste projeto, embora esse programa pode ser usado para outras finalidades.
         Vamos começar exemplificando como funciona o software do projeto(por ser mais simples) e logo em seguida vamos ao projeto do firmware.

O software do projeto:

         O aplicativo de controle do motor de passo é descrito na figura a seguir:

         Na figura pode ser visualizado 2 botões de envio de dados, sendo possível enviar o comando de velocidade e de giro de forma alternada sem ter que ficar reescrevendo na caixa de texto, sempre que uma porta serial virtual(geralmente quando se usa um emulador usb/serial) for conectada não é necessário reiniciar o aplicativo pois ao clicar para ver as portas de comunicação disponível o aplicativo já busca no windows quais estão ativas.Para que ele rode perfeitamente no seu computador você deve estar com o framework(a partir da versão 3.5) da microsoft atualizado.

link para download do aplicativo:
https://drive.google.com/file/d/0Bx9JeN0dmzXxN192eEUzaW1iRUk/edit?usp=sharing


O projeto:
       
        Quando fiz este projeto eu tinha em mente fazer algo que pudesse ser usado em uma CNC qualquer(pois A CNC pode ser de furação,deposição(impressora 3D) ou fresagem) logo os comando enviados devem controlar os três motores de forma independente e ao mesmo tempo ter a possibilidade de mudar a velocidade,mas para que os motores não extrapolem o limite físico da CNC, já previ a utilização de seis chaves fim de curso para sinalizar os limites de cada eixo(inicio e fim).
        O comando são enviados por pacotes com a seguinte formatação <XAAYAAZAA> a cada envio de dados o hardware irá devolver um '*' para indica que o comando foi recebido pelo microcontrolador, o carácter 'X' corresponde ao comando a ser efetuado pelo eixo X, o carácter 'Y' corresponde ao comando a ser efetuado pelo eixo Y e o carácter 'Z' corresponde ao comando a ser efetuado pelo eixo Z, já os caracteres 'AA' correspondem ao valor referente ao comandos.A lista de comandos é:
N ->Este comando não faz nada, somente para fechar o pacote.
P ->Este comando manda parar o motor imediatamente.
I ->Este comando manda o motor ir até o inicio do eixo,gira até chegar na chave fim de curso.
F ->Este comando manda o motor ir até o fim do eixo,gira até chegar na chave fim de curso.
H ->Gira um numero de pulsos ('AA' ) no sentido horário, varia de 00 a 99.
A ->Gira um numero de pulsos ('AA' ) no sentido anti-horário, varia de 00 a 99.
V ->Configura a velocidade do motor para um valor qualquer('AA'), varia de 00 a 99.
        Por exemplo vamos supor que eu queira a velocidade  motor no eixo 'X' igual a 10 do eixo 'Y' igual a 50 e do eixo 'Z' igual a 1, então basta enviar o comando "<V10V50V01>" , vamos supor agora que eu queira que o motor do eixo 'X' dê 10 passos no sentido horário, do eixo 'Y' dê 90 passos no sentido horário e do eixo 'Z' dê 50 passos no sentido anti-horário, então é só enviar o comando "<H10H90A50>", se por ventura eu decidir parar o motor do eixo 'Y' enquanto o comando anterior é executado basta enviar "<N00P00N00>" . Para melhor ilustrar isso veja o vídeo:


 

O Código fonte:

///************************************************************************/
//                             Placa de controle de uma CNC com 3 eixos
// EFUSE = 0XFF | LOCK BIT = 0XFF(sem cod. proteção) | FUSE HBIT = 0XD8 | FUSE LBIT = 0XC7

//  Version    : 1.0
//  microcontrolador : AVR ATMega328P
//  Autor        : Aguivone
//  descrição  : CNC via porta serial.
//  data : 25/05/2014.

////AVISOS:
// DESABILITAR OTIMIZAÇOES - 01, Se não dasabilitar pode fazer o programa travar ou rodar de modo errado.
// Atenção ao pino de reset sempre deve estar ligado ao +5V. 
//************************************************************************/

#define F_CPU 16000000UL  // 16 MHz
#include <avr/io.h>
#include <avr/interrupt.h>
#include <stdio.h>
#include <util/delay.h>
#include <string.h>
#include <avr/eeprom.h>//usado para acessar memoria
// definições auxiliaries
#define liga_pino(x,y) x |= _BV(y)
#define desliga_pino(x,y) x &= ~(_BV(y))
#define alterna_pino(x,y) x ^= _BV(y)
#define estado_pino(x,y) ((x & _BV(y)) == _BV(y)) //o pino deve estar como entrada ,testa se está em nivel logico 1
// definições dos pinos de entrada
#define SI_X   PC0//sensor de inicio da posição no eixo X
#define SF_X   PC1//sensor de fim da posição no eixo X
#define SI_Y   PC2//sensor de inicio da posição no eixo Y
#define SF_Y   PC3//sensor de fim da posição no eixo Y
#define SI_Z   PC4//sensor de inicio da posição no eixo Z
#define SF_Z   PC5//sensor de fim da posição no eixo Z
// definições dos pinos de saidas(motores)
#define MX_D   PB2//motor de delocamento no eixo X
#define MX_C   PB3
#define MX_B   PB4
#define MX_A   PB5

#define MY_A   PD6//motor de delocamento no eixo Y
#define MY_B   PD7
#define MY_C   PB0
#define MY_D   PB1

#define MZ_D   PD2//motor de delocamento no eixo Z
#define MZ_C   PD3
#define MZ_B   PD4
#define MZ_A   PD5




///variaveis usadas na comunicação serial

unsigned char caracter;//
unsigned int  tam_buffer;//usado para contar o tamanho do buffer de recepção
unsigned char buffer_serial[12];//buffer da serial
volatile char rec_buffer ='S';//usado para ler o buffer de recepção (volatile para funcionar pois sempre ira retornar mesmo valor)


//variaveis dos motores

unsigned char comandos[]={'P','P','P'};
unsigned int  num_passo[]={0,0,0};
unsigned int  pos_motor[]={0,0,0};
unsigned char sent_giro[]={'A','A','A'};//sentido da ultima rotação


//variaveis de tempo
unsigned int tempo[]={0,0,0};//tempo de comparação
unsigned int vel_motor[]={50,50,50};

//variaveis dos sensores
unsigned char status_sensor[]={0,0,0};//sensores de 0 a 2.
// I- inicio,F- fim,C- em area de deslocamento

///////////////////////////////////////////////funçoes usadas pela usart////////////////////////////////////////////////

void serial_inicializa(int BAUD)
{
    int valor = ((F_CPU/16)/BAUD)-1; //verifique se na simulação não está habilitado pra divir por oito o clock do microcontrolador
    /* baud rate */
    UCSR0A = 0X20;
    /* habilita receiver and transmitter buffers */
    UBRR0H = (unsigned char)(valor>>8);
    UBRR0L = (unsigned char)valor;
    /* habilita receiver and transmitter buffers */
    UCSR0B = 0X98;//deve se habilitar somente a interrupção de recepção para não travar o microcontrolador
    /* modo assicrono,8 bits , 1 stop bit, assincrono , sem paridade */
    UCSR0C = 0X06;
    //UCSR0C = (1<<USBS0)|(3<<UCSZ00);
}

void impri_serial(const char frase[20])//já tem caracter null embutido
{
    unsigned int indice=0;
    unsigned int tamanho = strlen(frase);
    while(indice<=tamanho)///veja que o programa pode travar se aqui não tiver as duas aspas
    {
        while ( !( UCSR0A & (1<<UDRE0)) ); //espera transmitir para enviar um novo
        UDR0 =frase[indice];
        indice++;
        while ( !( UCSR0A & (1<<UDRE0)) ); //espera transmitir desabilita a transmissão
    }
}

void escreve_caracter(const char carac)
{
    while ( !( UCSR0A & (1<<UDRE0)) ); //espera transmitir para enviar um novo
    UDR0 =carac;
    while ( !( UCSR0A & (1<<UDRE0)) ); //espera transmitir desabilita a transmissão
}

void inicializa_timer0(void)
{
    TCCR0A = 0X00;//não tem uso para o timer,configura para modo normal
    // TCCR0B = 0x01; //pre escaler dividido por 1
    //  TCCR0B = 0x02; //pre escaler dividido por 8
    //  TCCR0B = 0x03; //pre escaler dividido por 64
    TCCR0B = 0x04; //pre escaler dividido por 256
    //  TCCR0B = 0x05; //pre escaler dividido por 1024
    //uma interrupção a cada 64ms
    TIMSK0 = 0x01; //habilita interrupção do timer 0(estouro do contador)
}
void parar_timer(void)
{
    TIMSK0 = 0x00; //desabilita interrupção do timer 0(estouro do contador)
}
void reiniciar_timer(void)
{
    TIMSK0 = 0x01; //habilita interrupção do timer 0(estouro do contador)
}

//////////////////////////interrupção requerida para usar a serial///////////////////////////////////
ISR(USART_RX_vect)
{
    caracter = (char)UDR0;
    if(caracter == '>')
    {
        if((tam_buffer<12)&&(tam_buffer>9))//max do display
        {
            
            rec_buffer ='F';//dados
            
        }
        else
        {
            rec_buffer = 'S';//pede pra ignorar vetor - zera
        }
    }
    if((caracter != '>')&&(rec_buffer == 'I'))
    {
        buffer_serial[tam_buffer]=caracter;
        tam_buffer++;
    }
    if(caracter == '<')
    {
        rec_buffer ='I';
        buffer_serial[0]='<';
        tam_buffer=1;
    }
}
void parar_motor(int eixo)
{
    if(eixo == 0)
    {//eixo X
        desliga_pino(PORTB,MX_A);
        desliga_pino(PORTB,MX_B);
        desliga_pino(PORTB,MX_C);
        desliga_pino(PORTB,MX_D);
    }
    else
    {
        if(eixo == 1)
        {// eixo Y
            desliga_pino(PORTD,MY_A);
            desliga_pino(PORTD,MY_B);
            desliga_pino(PORTB,MY_C);
            desliga_pino(PORTB,MY_D);
        }
        else
        {//eixo Z (2)
            desliga_pino(PORTD,MZ_A);
            desliga_pino(PORTD,MZ_B);
            desliga_pino(PORTD,MZ_C);
            desliga_pino(PORTD,MZ_D);
        }
    }
   if((comandos[eixo]=='A')||(comandos[eixo]=='F'))
      //isso compensa o ultimo decremento que não foi usado
 {
  if(pos_motor[eixo]== 3)
  {
     pos_motor[eixo]= 0 ;
  }
  else
  {
   pos_motor[eixo]++;
  }
 }
     if((comandos[eixo]=='I')||(comandos[eixo]=='H'))
       //isso compensa o ultimo incremento que não foi usado
 {
  if(pos_motor[eixo]== 0)
  {
   pos_motor[eixo]= 3 ;
  }
  else
  {
   pos_motor[eixo]--;
  } 
 }
    num_passo[eixo]=0;
    tempo[eixo]=0;//zera tempo
} void atualiza_sensores() { if((estado_pino(PINC,SI_X)==1)&&(estado_pino(PINC,SF_X)==1)) { status_sensor[0] = 'C';//indica que está no curso do eixo } else { if(estado_pino(PINC,SI_X)==1) { status_sensor[0] = 'I';//indica que está no inicio do eixo } else { status_sensor[0] = 'F';//indica que está no fim do eixo } } if((estado_pino(PINC,SI_Y))&&(estado_pino(PINC,SF_Y))) { status_sensor[1] = 'C';//indica que está no curso do eixo } else { if(estado_pino(PINC,SI_Y)) { status_sensor[1] = 'I';//indica que está no inicio do eixo } else { status_sensor[1] = 'F';//indica que está no fim do eixo } } if((estado_pino(PINC,SI_Z))&&(estado_pino(PINC,SF_Z))) { status_sensor[2] = 'C';//indica que está no curso do eixo } else { if(estado_pino(PINC,SI_Z)) { status_sensor[2] = 'I';//indica que está no inicio do eixo } else { status_sensor[2] = 'F';//indica que está no fim do eixo } } } void giro_anti_horario(int eixo) { switch(pos_motor[eixo]) { case 0: {//passo 1 if(eixo == 0) {//eixo X liga_pino(PORTB,MX_A); desliga_pino(PORTB,MX_B); desliga_pino(PORTB,MX_C); desliga_pino(PORTB,MX_D); } else { if(eixo == 1) {// eixo Y liga_pino(PORTD,MY_A); desliga_pino(PORTD,MY_B); desliga_pino(PORTB,MY_C); desliga_pino(PORTB,MY_D); } else {//eixo Z (2) liga_pino(PORTD,MZ_A); desliga_pino(PORTD,MZ_B); desliga_pino(PORTD,MZ_C); desliga_pino(PORTD,MZ_D); } } pos_motor[eixo]=3; }break; case 1: {//passo 2 if(eixo == 0) {//eixo X liga_pino(PORTB,MX_B); desliga_pino(PORTB,MX_A); desliga_pino(PORTB,MX_C); desliga_pino(PORTB,MX_D); } else { if(eixo == 1) {// eixo Y liga_pino(PORTD,MY_B); desliga_pino(PORTD,MY_A); desliga_pino(PORTB,MY_C); desliga_pino(PORTB,MY_D); } else {//eixo Z (2) liga_pino(PORTD,MZ_B); desliga_pino(PORTD,MZ_A); desliga_pino(PORTD,MZ_C); desliga_pino(PORTD,MZ_D); } } pos_motor[eixo]=0; }break; case 2: {//passo 3 if(eixo == 0) {//eixo X liga_pino(PORTB,MX_C); desliga_pino(PORTB,MX_B); desliga_pino(PORTB,MX_A); desliga_pino(PORTB,MX_D); } else { if(eixo == 1) {// eixo Y liga_pino(PORTB,MY_C); desliga_pino(PORTD,MY_B); desliga_pino(PORTD,MY_A); desliga_pino(PORTB,MY_D); } else {//eixo Z (2) liga_pino(PORTD,MZ_C); desliga_pino(PORTD,MZ_B); desliga_pino(PORTD,MZ_A); desliga_pino(PORTD,MZ_D); } } pos_motor[eixo]=1; }break; case 3: {//passo 4 if(eixo == 0) {//eixo X liga_pino(PORTB,MX_D); desliga_pino(PORTB,MX_B); desliga_pino(PORTB,MX_C); desliga_pino(PORTB,MX_A); } else { if(eixo == 1) {// eixo Y liga_pino(PORTB,MY_D); desliga_pino(PORTD,MY_B); desliga_pino(PORTB,MY_C); desliga_pino(PORTD,MY_A); } else {//eixo Z (2) liga_pino(PORTD,MZ_D); desliga_pino(PORTD,MZ_B); desliga_pino(PORTD,MZ_C); desliga_pino(PORTD,MZ_A); } } pos_motor[eixo]= 2; }break; } } void giro_horario(int eixo) { switch(pos_motor[eixo]) { case 0: {//passo 1 if(eixo == 0) {//eixo X liga_pino(PORTB,MX_A); desliga_pino(PORTB,MX_B); desliga_pino(PORTB,MX_C); desliga_pino(PORTB,MX_D); } else { if(eixo == 1) {// eixo Y liga_pino(PORTD,MY_A); desliga_pino(PORTD,MY_B); desliga_pino(PORTB,MY_C); desliga_pino(PORTB,MY_D); } else {//eixo Z (2) liga_pino(PORTD,MZ_A); desliga_pino(PORTD,MZ_B); desliga_pino(PORTD,MZ_C); desliga_pino(PORTD,MZ_D); } } pos_motor[eixo]=1; }break; case 1: {//passo 2 if(eixo == 0) {//eixo X liga_pino(PORTB,MX_B); desliga_pino(PORTB,MX_A); desliga_pino(PORTB,MX_C); desliga_pino(PORTB,MX_D); } else { if(eixo == 1) {// eixo Y liga_pino(PORTD,MY_B); desliga_pino(PORTD,MY_A); desliga_pino(PORTB,MY_C); desliga_pino(PORTB,MY_D); } else {//eixo Z (2) liga_pino(PORTD,MZ_B); desliga_pino(PORTD,MZ_A); desliga_pino(PORTD,MZ_C); desliga_pino(PORTD,MZ_D); } } pos_motor[eixo]=2; }break; case 2: {//passo 3 if(eixo == 0) {//eixo X liga_pino(PORTB,MX_C); desliga_pino(PORTB,MX_B); desliga_pino(PORTB,MX_A); desliga_pino(PORTB,MX_D); } else { if(eixo == 1) {// eixo Y liga_pino(PORTB,MY_C); desliga_pino(PORTD,MY_B); desliga_pino(PORTD,MY_A); desliga_pino(PORTB,MY_D); } else {//eixo Z (2) liga_pino(PORTD,MZ_C); desliga_pino(PORTD,MZ_B); desliga_pino(PORTD,MZ_A); desliga_pino(PORTD,MZ_D); } } pos_motor[eixo]=3; }break; case 3: {//passo 4 if(eixo == 0) {//eixo X liga_pino(PORTB,MX_D); desliga_pino(PORTB,MX_B); desliga_pino(PORTB,MX_C); desliga_pino(PORTB,MX_A); } else { if(eixo == 1) {// eixo Y liga_pino(PORTB,MY_D); desliga_pino(PORTD,MY_B); desliga_pino(PORTB,MY_C); desliga_pino(PORTD,MY_A); } else {//eixo Z (2) liga_pino(PORTD,MZ_D); desliga_pino(PORTD,MZ_B); desliga_pino(PORTD,MZ_C); desliga_pino(PORTD,MZ_A); } } pos_motor[eixo]= 0; }break; } } int valor_decimal(char dezena,char unidade) { int retorno=0; dezena = dezena - 0X30;//converte para int unidade = unidade - 0X30; if(dezena == 0X00) { retorno = unidade; } else { retorno = (dezena*10)+unidade; } return(retorno); } void compensa_giro(int passo) { //como o valor que está na variavel seria a prox posição, mas o giro foi invertdo //logo deve voltar a posição atual e então colocar o proxima posição conforme a seguir: switch(pos_motor[passo]) { case 0: { pos_motor[passo]=2; }break; case 1: { pos_motor[passo]=3; }break; case 2: { pos_motor[passo]=0; }break; case 3: { pos_motor[passo]=1; }break; } } void ger_motor(int rodar) { switch(comandos[rodar]) { case'N': {//não altera o estado atual do motor mas evita a perda de tempo checando as proximas possibilidades }break; case'P': {//manda o motor parar if(num_passo > 0)//testa se motor está energizado? { parar_motor(rodar); } }break; case'I': {//manda motor voltar a origem do eixo if(status_sensor[rodar] != 'I')//ainda não chegou na origem do eixo { if(sent_giro[rodar]=='H')//verifica de qual lado foi o ultimo giro {//isso indica que o giro deve ser invertido logo a proxima posição deve ser compensada compensa_giro(rodar); giro_anti_horario(rodar); }else { giro_anti_horario(rodar); } sent_giro[rodar]='A'; } else {//se já estiver na origem não movimenta mais parar_motor(rodar); num_passo[rodar]=0; comandos[rodar]='P';//troca o comando para não ficar executando essa rotina } }break; case'F': {//manda motor ir para o fim do eixo if(status_sensor[rodar] != 'F')//ainda não chegou no fim do eixo { if(sent_giro[rodar]=='H') { giro_horario(rodar); }else {//compensa inversão de giro compensa_giro(rodar); giro_horario(rodar); } sent_giro[rodar]='H'; } else {//chegou ao fim do eixo. parar_motor(rodar); num_passo[rodar]=0; comandos[rodar]='P';//troca o comando para não ficar executando essa rotina } }break; case'A': {//manda motor girar em sentido anti-horario,rumo ao inicio de deslocamento if(status_sensor[rodar] != 'I')//ainda não chegou na origem do eixo { if(sent_giro[rodar]=='H')//verifica de qual lado foi o ultimo giro {//isso indica que o giro deve ser invertido logo a proxima posição deve ser compensada compensa_giro(rodar); giro_anti_horario(rodar); }else { giro_anti_horario(rodar); } sent_giro[rodar]='A'; if(num_passo[rodar] <= 0) { parar_motor(rodar); num_passo[rodar]=0; comandos[rodar]='P';//troca o comando para não ficar executando essa rotina } else { num_passo[rodar]--; } } else {//se já estiver na origem não movimenta mais parar_motor(rodar); num_passo[rodar]=0; comandos[rodar]='P';//troca o comando para não ficar executando essa rotina } }break; case'H': {//manda motor girar em sentido horario, rumo ao fim do deslocamento. if(status_sensor[rodar] != 'F')//ainda não chegou no fim do eixo { if(sent_giro[rodar]=='H') { giro_horario(rodar); }else {//compensa inversão de giro compensa_giro(rodar); giro_horario(rodar); } sent_giro[rodar]='H'; if(num_passo[rodar] <= 0) { parar_motor(rodar); num_passo[rodar]=0; comandos[rodar]='P';//troca o comando para não ficar executando essa rotina } else { num_passo[rodar]--; } } else {//chegou ao fim do eixo. parar_motor(rodar); num_passo[rodar]=0; comandos[rodar]='P';//troca o comando para não ficar executando essa rotina } }break; } } ISR(TIMER0_OVF_vect) {//interrupção overflow // divisão: tempo de cada interrupção: // 1 -> 16s ; // 8 -> 128us ; // 64 -> 1,024ms // 256 -> 4,096ms // 1024 -> 16,38ms. int xis=0; while(xis<3) { tempo[xis]++; if(tempo[xis] >= vel_motor[xis])//compara tempo de acionar saida { atualiza_sensores();//checa o estado das chaves de inicio e fim de curso. ger_motor(xis); tempo[xis]=0;//zera tempo } xis++; } } int main(void) { DDRB = 0XFF; //são saidas PORTB = 0X00;//zera saidas DDRC = 0X00; // configurado como entrada (sensores) DDRD = 0XFE; //pinos como saida exceto D0(RX da serial) PORTD = 0X00;//zera saidas DIDR0 = 0X00;////desabilita entrada analogico(reduz consumo).evita problemas quando esse pino é usado como I/O serial_inicializa(9600);// verifique se o clock não está dividido por 8! inicializa_timer0(); //parar_timer(); reiniciar_timer(); sei(); //habilita interrupções parar_timer(); impri_serial(" CNC - 3 Eixos."); reiniciar_timer(); /*for(;;) { //impri_serial("teste \n\r "); //_delay_ms(500); }*/ for(;;) { if(rec_buffer =='F')//tem dados pra ler { int temp=1; int com=0; parar_timer(); while(temp<9) { if(buffer_serial[temp]=='V') {//altera velocidade vel_motor[com]=valor_decimal(buffer_serial[temp +1],buffer_serial[temp +2]); } else { comandos[com]=buffer_serial[temp]; num_passo[com]=valor_decimal(buffer_serial[temp +1],buffer_serial[temp +2]); } com++; temp=temp+3; } escreve_caracter('*'); reiniciar_timer(); rec_buffer ='S';//fim de processamento de dados } } }