Introduction: Extending the Control Power of the "Stepper Servo Motor" + Arduino - Pt3
Now that we have a functional servo, which was previously produced in the first two videos of the series entitled Motor Step, now we’re moving on and discussing how to increase the control arsenal by introducing a control via the Arduino serial. Continuing this series, we will implement command functions for the servo that will not only position it, but also create adjustments, and even measure its true position.
Want to see the first two videos in the series? Access: Part 1 and Part 2.
Step 1: Key Features
- Allows commands through serial communication.
- Flexibility in software configurations, allowing for varying forms of control.
- Flexibility in hardware assembly, allowing variations of motors, drivers, and sensors.
- Return of the actual position information by reading the sensor.
Step 2: Assembly
We will continue to use the same old assembly. But, we will leave only the axis reading potentiometer.
The potentiometer will continue to operate as a sensor of the current axis position. To do this, attach the motor shaft to the potentiometer knob.
We will connect the potentiometer to analog input A0.
• The AXIS will connect to pin A0 (purple wire)
• 5V power supply (green wire)
• The GND reference (black wire)
ATTENTION!!!
Before attaching the sensor potentiometer to the shaft, test the assembly to verify that the rotation is occurring in the correct direction. When driving a position increase, the motor must rotate in order to increase the sensor potentiometer.
If the rotation is occurring in reverse, simply reverse the polarization of the potentiometer.
As the pitch motor torque is usually high, it can damage the sensor potentiometer by trying to get you into a position that can’t be reached.
Step 3: Mounting the Circuit
Step 4: Understanding the Program (Declarations)
Step 5: Arduino Source Code
Global Declarations:
(Constants)
We start by defining constants that will represent Arduino pins D2, D3, D4, and D5. These pins will be responsible for transmitting the drive sequence to the driver.
The constant SHAFT refers to pin A0 used by the sensor potentiometer.
// Declaração Global de constantes que representarão os pinos do Arduino
const byte INPUT_1 = 2; //Pinos de controle do L298N const byte INPUT_2 = 3; const byte INPUT_3 = 4; const byte INPUT_4 = 5; const byte EIXO = A7; // Potenciômetro do EIXO.
Global Declarations:
(Variables)
Now we will see that we have new variables for the control of the Step Servo Motor.
inc_dec - Stores the increment / decrement of the position.
delay_in_passes - sets the delay for step change.
tolerance - sets the accuracy of position adjustment.
command_read - stores the value of the target position received by the command.
axis_read - stores the value of the current axis position.
step - stores the current step of the step sequence.
lower_limit - stores the value used to set the minimum adjustment position.
upper_limit - stores the value used to set the maximum adjustment position.
command_received - stores the command received by the serial.
full-reception - indicates if data reception has been completed.
//Variáveis globais
int inc_dec = 1; //Valor usado como incremento e decremento da posição (Comandos + e -) int retardo_entre_os_passos = 3; //constante que determinará a retardo entre os passos do ajuste do motor int tolerancia = 1; //constante que determinará uma tolerância mínima para o ajuste int leitura_comando = 510; // Variável que armazena o valor da leitura do COMANDO. int leitura_eixo = 0; // Variável que armazena o valor da leitura do SENSOR DO EIXO. int passo = 0; //Variável que armazena o passo atual. int limite_inferior = 420; //limite inferior para o posicionamento int limite_superior = 620; //limite superior para o posicionamento String comando_recebido = ""; //String que armazena os dados recebido boolean recepcao_completa = false; //sinalizador de dados aguardando leitura
Step 6: Understanding the Program (Setup)
Setup ()
In the setup () function, we set the digital pins as outputs, and the analog pin as input. Then, we start the Serial function. For the reception, we reserved 200 bytes for the received command (more than enough).
We read the initial position of the axis and execute a function that sends a message of current configuration and list of commands to the user through the serial.
void setup() {
// Ajustando os pinos do Arduino para operarem como saídas digitais pinMode(INPUT_1, OUTPUT); pinMode(INPUT_2, OUTPUT); pinMode(INPUT_3, OUTPUT); pinMode(INPUT_4, OUTPUT); // Ajustando os pinos do Arduino resposáveis pela leitura do potenciômetro pinMode(EIXO, INPUT); //Inicia a comunicação serial Serial.begin(9600); // reserva 200 bytes de buffer para dados recebidos comando_recebido.reserve(200); // Determina sua posição inicial leitura_eixo = analogRead(EIXO); //envia uma mensagem de inicio pela serial mensagem(); }
Step 7: Understanding the Program (message)
The message function is a series of "prints,” sent by the serial to make the manipulation of the program friendlier. It serves as guidance.
It still makes a call to the command interpreter function requesting a reading of the values of the variables. We will see this function later.
Note:
The message function is optional and can be removed from the program, since the user is already accustomed to the list of commands.
void mensagem()
{ /* Envia pela serial uma mensagem inicial com algumas informações como ajustes do servo e comandos disponíveis. Esta função é opcional. Pode ser retirada do programa sem consequencias */ Serial.println("Servo com Motor de Passo e Controle Serial\n"); Serial.println("Ajustes atuais:"); interpretador("L"); Serial.println(""); Serial.println("Lista de comandos\n"); Serial.println("L - Ler as configuracoes atuais"); Serial.println("A# - Ajusta a posicao para #"); Serial.println("M# - Ajusta para a posicao MEDIA "); Serial.println("R# - Ajusta a rapidez com que os passos sao dados"); Serial.println("S# - Ajusta o valor do limite superior"); Serial.println("I# - Ajusta o valor do limite inferior"); Serial.println("T# - Ajusta o valor de desvio toleravel"); Serial.println("+# - Incrementa o passo em #"); Serial.println("-# - Decrementa o passo em #"); }
In images we have a example of message sent - Information and Commands
Step 8: Understanding the Program (Loop)
Loop ():
In the loop () function, we re-read the current position of the axis and then check if there is any complete data reception, using the signal full_recognition. If this variable is true, we pass the program control to the command interpreting function. Repeat the same adjustment process we did before, using the value contained in the variable read_command as a target of adjustment, and tolerance as a determinant of the accuracy of the adjustment.
void loop() {
//leitura do potenciômetro de posição do eixo leitura_eixo = analogRead(EIXO); // verifica se há dados disponíveis if (recepcao_completa == true) { //Envia o comando recebido para a função que interpretará o comando interpretador(comando_recebido); //reinicia as variáveis de recepção comando_recebido = ""; recepcao_completa = false; } // Avaliação da direção do movimento if (leitura_eixo < (leitura_comando - tolerancia)) { girar(1); //Girar o EIXO no sentido de AUMENTAR a leitura do sensor } if (leitura_eixo > (leitura_comando + tolerancia)) { girar(-1); //Girar o EIXO no sentido de REDUZIR a leitura do sensor } // Aguarde para repetir delay(retardo_entre_os_passos); }
For the calculation of the target position, it is essential that we include a value that indicates the tolerance of the positioning. This will allow us to deal with noise in the reading simply by increasing the target range.
Step 9: Understanding the Program (rotate)
The spin function works in the same way that we saw earlier.
Step 10: Rotate (int Direction)
The spin function will receive a parameter that indicates which side the motor should rotate.
Evaluating the values that occur in the loop, as we have just seen, sends this parameter.
The value of the "direction" parameter will determine whether the step should be incremented or decremented.
We have created this function separately only to better illustrate how the program works. We could have included this code directly in the evaluation that occurs in the loop.
//Função para girar o motor na direcao avaliada
void girar(int direcao) { // Girar INCREMENTANDO o PASSO if (direcao > 0) { passo++; if (passo > 3) { passo = 0; } } //Girar DECREMENTANDO o passo else { passo--; if (passo < 0 ) { passo = 3; } } //Atualiza o passo ajustar_passo(passo); }
Step 11: Understanding the Program (step_adjust)
The step_adjustment function activates the reels in the correct sequence.
//Função para atualização do passo
void ajustar_passo (int bobina) { switch (bobina) { //PASSO 1 case 0: digitalWrite(INPUT_1, HIGH); digitalWrite(INPUT_2, LOW); digitalWrite(INPUT_3, LOW); digitalWrite(INPUT_4, LOW); break; ///PASSO 2 case 1: digitalWrite(INPUT_1, LOW); digitalWrite(INPUT_2, HIGH); digitalWrite(INPUT_3, LOW); digitalWrite(INPUT_4, LOW); break; //PASSO 3 case 2: digitalWrite(INPUT_1, LOW); digitalWrite(INPUT_2, LOW); digitalWrite(INPUT_3, HIGH); digitalWrite(INPUT_4, LOW); break; //PASSO 4 case 3: digitalWrite(INPUT_1, LOW); digitalWrite(INPUT_2, LOW); digitalWrite(INPUT_3, LOW); digitalWrite(INPUT_4, HIGH); break; } }
Step 12: Understanding the Program (serialEvent)
serialEvent
The serialEvent () function is an Arduino function that automatically triggers whenever data is received by the serial.
Note that we don’t need to make explicit calls to this function, because once data is received, the program flow is interrupted and this function is called to manipulate the received data.
We do this by checking for bytes in the Arduino serial receive buffer by using the Serial.available () function. We store each byte in the character variable and add the command_received variable until a newline character is received, signaling the end of the command, or until the bytes of the receive buffer run out.
void serialEvent()
{ /* A função SerialEvent ocorre sempre que um novo byte chegar na entrada RX, interrompendo a execução normal do programa e retornando depois de executada */ while (Serial.available()) { // captura o novo byte recebido e armazena como caracter char caracter = (char)Serial.read(); // adiciona o novo caracter a variável comando_recebido: comando_recebido += caracter; // se o novo caracter recebido é um caracter de nova linha (\n), //então muda o valor do sinalizador de recepção completa para que o //possamos utilizar o comando recebido if (caracter == '\n') { recepcao_completa = true; } } }
Step 13: Understanding the Program (Command Interpreter)
Cascade of 'if' ...
Step 14: Command Interpreter - "L" Command
The command interpreter function is a collection of checks of the first letter of the received command. Each letter represents a command, and the following data will serve as arguments for this command, if they exist.
The first command is the "L" command that performs readouts of all control variables and sends them to the user through the serial.
//Esta função é responsável pela interpretação de cada comando
void interpretador(String comando) { if (comando.startsWith("L")) //LEITURA do estado atual { Serial.println("___________________________"); Serial.print("Posicao (und.): "); Serial.println(leitura_eixo); Serial.print("Retardo entre os passos (ms): "); Serial.println(retardo_entre_os_passos); Serial.print("Tolerancia (und.): "); Serial.println(tolerancia); Serial.print("Limite Inferior (und.): "); Serial.println(limite_inferior); Serial.print("Limite Superior (und.): "); Serial.println(limite_superior); Serial.println("___________________________"); Serial.flush(); }
Step 15: Command Interpreter - "T" Command
The "T" command reads the remaining bytes of the command variable after the first letter and uses these values to adjust the value of the tolerance variable.
Note that we use the .substring method to cut from the second position (the count of the string positions start at zero), up to the total length of the command variable. To get the length, we use the .length () method.
Finally, we use the .toInt () method to convert this value to an integer.
//determina o valor da TOLERÂNCIA do ajuste
if (comando.startsWith("T")) { //obtém somente o valor do comando recebido e o converte para inteiro tolerancia = comando.substring(1, comando.length()).toInt(); }
Step 16: Command Interpreter - "R" Command
The "R" command is responsible for the adjustment of the delay_intervals variable. This delay is important to control the speed of movement. It replaced the variable speed we used before (we just changed the name).
The argument for this command is captured in the same way as the previous command.
Once the value is received, it is checked. The value of this delay can’t be too small because it would make the motor skip steps. In our case, the limit is 3 milliseconds.
// Define o RETARDO entre cada mudança de passo
if (comando.startsWith("R")) { //obtém somente o valor do comando recebido e o converte para inteiro retardo_entre_os_passos = comando.substring(1, comando.length()).toInt(); /* Verifica se este valor é menor que 3ms. Se a mudança de passos for muito rápida o motor pode não ser capaz de girar e pode perder passos. Se uma valor menor que 3 for recebido, este valor será ignorado e o valor 3 será atribuido. */ if(retardo_entre_os_passos < 3){retardo_entre_os_passos =3;} }
Step 17: Command Interpreter - "I" and "S" Commands
The "I" and "S" commands are used to adjust the value of the variables that control the limits of servo movement.
Being "I" stops the lower limit and "S" for the upper limit.
The reception of the values occurs in much the same way as in the previous cases.
//Define o limite INFERIOR para posicionamento.
if (comando.startsWith("I")) { //obtém somente o valor do comando recebido e o converte para inteiro limite_inferior = comando.substring(1, comando.length()).toInt(); /* Verifica se este valor é menor que 0. Como estamos utilizando diretamente a leitura da porta analógica A0 (0 a 1023), este valor não pode ser negativo. */ if(limite_inferior < 0){limite_inferior = 0;} } //Define o limite SUPERIOR para posicionamento if (comando.startsWith("S")) { //obtém somente o valor do comando recebido e o converte para inteiro limite_superior = comando.substring(1, comando.length()).toInt(); /* Verifica se este valor é maior que 1023. Como estamos utilizando diretamente a leitura da porta analógica A0 (0 a 1023), este valor não pode ser maior que 1023. */ if(limite_superior > 1023){limite_superior = 1023;} }
Step 18: Command Interpreter - "M" Command
The "M" command calculates the value of the AVERAGE position between the upper and lower limits and assigns this value to the variable read_command, thus becoming the target of the adjustment.
After the "M" command, the servo will always be positioned in the intermediate position, between the maximum and minimum position adjusted.
//Ajusta o servo para uma posição MÉDIA entre os LIMITES
if (comando.startsWith("M")) { //Calcula a posição MÉDIA leitura_comando = (limite_superior + limite_inferior) / 2; }
Step 19: Command Interpreter - "+" and "-" Commands
The "+" and "-" commands increase and decrease, respectively, the position of the servo, in steps defined by the value of the variable inc_dec.
The value of this variable can be set as an argument of these two commands.
Once adjusted, it is not necessary to re-send the increment or decrement value, unless the intention is to actually change it.
//INCREMENTA a posição um ajuste além da tolerância
if (comando.startsWith("+")) { //obtém somente o valor do comando recebido e o converte para inteiro int temp = comando.substring(1, comando.length()).toInt(); // Verifica se o valor recebido é maior que 0 if(temp > 0){inc_dec = temp;} //Calcula a nova posição leitura_comando = leitura_eixo + inc_dec; /* Verifica se a posição solicitada está dentro do limite SUPERIOR. Se for superado, o valor será ignorado e o valor limite será tomado como novo valor. */ if (leitura_comando > limite_superior) { leitura_comando = limite_superior; } } //DECREMENTA a posição um ajuste além da tolerância if (comando.startsWith("-")) { //obtém somente o valor do comando recebido e o converte para inteiro int temp = comando.substring(1, comando.length()).toInt(); // Verifica se o valor recebido é maior que 0 if(temp > 0){inc_dec = temp;} //Calcula a nova posição leitura_comando = leitura_eixo - inc_dec; /* Verifica se a posição solicitada está dentro do limite INFERIOR. Se for superado, o valor será ignorado e o valor limite será tomado como novo valor. */ if (leitura_comando < limite_inferior) { leitura_comando = limite_inferior; } }
Step 20: Command Interpreter - "A" Command
The "A" command is responsible for the servo position ADJUSTMENT. The new position is the argument of this command. If the argument is omitted, it will be considered zero and the servo will move to the minimum position.
//AJUSTA para a posição contida no comando
if (comando.startsWith("A")) { //obtém somente o valor do comando recebido e o converte para inteiro leitura_comando = comando.substring(1, comando.length()).toInt(); /* Verifica se a posição solicitada está entre os limites SUPERIOR e INFERIOR. Se algum deles for superado, o valor será ignorado e o valor limite será tomado como novo valor. */ if (leitura_comando > limite_superior) { leitura_comando = limite_superior; } if (leitura_comando < limite_inferior) { leitura_comando = limite_inferior; } } }
Step 21: New Support for NEMA 17
You can find the STL file for 3D printer in www.fernandok.com.