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
}
}
}
Bom dia!
ResponderExcluirVc controla os motores com a função switch, pela variavél eixo. A variável eixo é mudada pelos sensores? Como decide qnto cada motor vai girar?
Se vc ver no texto, basta colocar a quantidade de giros no comando de eixos assim ele o fará a quantidade de vezes indicada, no código quem gerencia isso e a variável "num_passo[rodar]", pois é ela que é setada!
ResponderExcluirOla!!!!
ResponderExcluircomo voce determina como os motores irão girar,por exemplo... se vc vai fazer uma letra " L" e simples pois os movimentos são uniforme para os eixos, mas quando é mais complexo tipo um " W " ou um " C " como determinar o sincronismo que irão girar o motor!
Você teria esse mesmo codigo para o compilador XC8?
ResponderExcluirAmigo boa noite , este programa ( O aplicativo de controle do motor de passo)que vc, fez vc poderia informar o código fonte . xcarlosmiranda@gmail.com
ResponderExcluir