Paso 19: Código: gatear
Arduino herramientas y conocimiento de Fundación
Si usted está buscando una buena introducción para Arduinos, entonces allí son un montón de Instructables para usted, aquí es una gran.
Me va a suponer que ha descargado e instala el IDE de Arduino y es capaces de subir el código a tu Arduino, el mencionado instructivo le guiará a través de todo eso y mucho más.
Código Resumen
En el paso anterior hemos decidido que sería aplicar el rastreo a través de "frames" de la animación que definiría la posición de cada uno de los servos en un momento dado en el tiempo.
El código completo que se adjunta como un archivo, pero voy a ir a través de la mayoría de las secciones aquí para que usted pueda entenderles. Si usted ya entiende, y eso es genial, ya que probablemente usted puede pensar de una manera mejor de hacer esto que hice.
Importar bibliotecas requeridas
La única librería que se necesita para que este código es "Servo.h" que está lleno de funciones útiles para que sea extremadamente fácil controlar servos desde un Arduino.
#include <Servo.h>
Definir Variables globales
Estas son las variables que pueden acceder desde cualquier lugar del código. Podemos usarlos para almacenar información que será necesario en una etapa posterior
Temporizador y Variables
El comando millis() devuelve el número de milisegundos desde la placa Arduino comenzó a ejecutar el programa actual. Podemos almacenar el resultado de millis() y luego compararlo con el resultado en una etapa posterior para determinar cuánto tiempo ha estado entre las dos llamadas.
long previousFrameMillis = 0; long previousInterpolationMillis = 0;
Variables de detalle de servo
Estas variables almacenan que pin cada servo está conectado, así como cuántos micros corresponden a min (retraído/bajado) y max (levantado/extendido)
//servo pins int LNpin = 7; //left neck muscle int RNpin = 6; //right neck muscle int LSpin = 5; //left shoulder int LEpin = 4; //left elbow int RSpin = 2; //right shoulder int REpin = 3; //right elbow //these are the 'min' and 'max' angles for each servo //can be inverted for servos that are in opposite orientation int LSMin = 1000; int LSMax = 1700; int RSMin = 2000; int RSMax = 1300; int LEMin = 1700; int LEMax = 1300; int REMin = 1300; int REMax = 1700; int LNMin = 1400; int LNMax = 1600; int RNMin = 1600; int RNMax = 1400;
Marcos
La primera impresión puede ser que acaba de programar los servos a la posición definida por el primer cuadro, esperar un retraso determinado (frameDuration) y luego ajustar los servos a la posición siguiente.
Esto dará como resultado una animación horrible, ya que los servos se saltan tan rápido como les sea posible a la especificado la posición y luego esperar para la siguiente instrucción.
La forma de evitar esto es interpolar entre marcos. En otras palabras, si la duración de mi marco es de 2 segundos, después de 1,75 segundos quiero los servos que tres cuartas partes (o 75%) de la manera entre 1 y 2.
Es un poco trivial de matemáticas para averiguar que el servo debe ser si sabemos cuánto del marco ha transcurrido como cociente. En palabras es sólo (cuadro anterior) +(the difference between the next and previous frames) * (porcentaje de marco duración transcurrido), esto se conoce como "interpolación lineal".
int frameDuration = 800; //length of a frame in milliseconds int frameElapsed = 0; //counter of milliseconds of this frame that have elapsed int interpolationDuration = 30; //number of milliseconds between interpolation steps
Aquí definimos los marcos reales. Nota que he utilizado 0 a 1000, donde 0 indica retraído/bajada y 1000 indica extendido/levantado. Podría haber elegido cualquier número y también podría haber opciones más lógicas, pero la gama me proporcionó un compromiso satisfactorio entre resolución y legibilidad.
Utilizaremos la función map() más adelante para asignar 0 a la variable LSMin y 1000 a LSMax variable que definimos anteriormente (obviamente este ejemplo es para el hombro izquierdo, pero sería el mismo proceso para todos los servos).
Si desea definir animaciones más complejas o más suaves podría fácilmente agregar más marcos y también utilizar números distintos de min/max. Una opción sería utilizar unos 8 marcos para hacer un agradable movimiento elíptico.
//frames for the crawling gait are stored here int currentFrameIndex = 0; int numFrames = 4; int crawlGaitRS[] = {0,1000,1000,0}; int crawlGaitRE[] = {0,0,1000,1000}; int crawlGaitLE[] = {1000,0,0,1000}; int crawlGaitLS[] = {0,0,1000,1000}; int crawlGaitLN[] = {1000,1000,0,1000}; int crawlGaitRN[] = {0,1000,1000,1000};
Para implementar este cálculo de la interpolación que necesitamos para mantener el marco anterior y el siguiente, así que hemos creado algunas variables para hacer eso.
//servo last frame micros int LSlast = 0; int RSlast = 0; int LElast = 0; int RElast = 0; int LNlast = 0; int RNlast = 0; //servo next frame micros int LSnext = 0; int RSnext = 0; int LEnext = 0; int REnext = 0; int LNnext = 0; int RNnext = 0; // variable used to store the current frame of animation int currentFrameIndex = 0;
Objetos de servo
Por último, creamos algunos objetos servo y asignarlos a variables. Estas son instancias de la clase de servo que se incluyeron en Servo.h y proporcionarán funciones útiles para el control de cada servo.
// create servo objects to control servos Servo LS; Servo RS; Servo LE; Servo RE; Servo LN; Servo RN;
Definir las funciones
Función de configuración de Arduino
La función setup() de Arduino es el primer bit del código que obtiene después de que se han definido las variables globales. Todo lo que se necesita aquí por ahora es unir los objetos del servo a sus broches y puesta en marcha del puerto Serial, en caso desee informe nada para debuggging.
void setup() { Serial.begin(9600); LS.attach(LSpin); RS.attach(RSpin); LE.attach(LEpin); RE.attach(REpin); LN.attach(LNpin); RN.attach(RNpin); }
Set siguiente fotograma
Esta función se llama una vez nuestros servos al final de un marco. Todo lo que está haciendo es:
- Incremento de la "currentFrameIndex" (a menos que han alcanzado el último fotograma, en cuyo caso lazos a marco 0)
- Almacenar la posición actual del marco como "último capítulo"
- Recuperar la posición siguiente del marco de la matriz de animación
void setNextFrame() { //if this was the last frame, start again if (currentFrameIndex < numFrames - 1) { currentFrameIndex++; } else { currentFrameIndex = 0; } //we have reached the destination frame, so store it as "last frame" LSlast = LSnext; RSlast = RSnext; LElast = LEnext; RElast = REnext; LNlast = LNnext; RNlast = RNnext; //generate new "next frame" LSnext = crawlGaitLS[currentFrameIndex]; RSnext = crawlGaitRS[currentFrameIndex]; LEnext = crawlGaitLE[currentFrameIndex]; REnext = crawlGaitRE[currentFrameIndex]; LNnext = crawlGaitLN[currentFrameIndex]; RNnext = crawlGaitRN[currentFrameIndex]; }
Función de la interpolación
Como se describió anteriormente, utilizamos una interpolación lineal para determinar exactamente en qué posición un servo debe estar en un momento dado entre dos fotogramas.
Mi profesor de ciencia de computadora siempre dijo que ser un buen programador era todo acerca de ser perezoso, si puede evitar escribir código varias veces, poniendo en una función, entonces lo hacen.
Esta función simplemente implementa la ecuación de la interpolación lineal entre dos cuadros mapas esa posición de marco a una posición de servo y aplica al objeto servo.
void writeInterpolatMicros(Servo servo, int prevFrame, int nextFrame, int servoMin, int servoMax, float elapsedRatio) { int interpolated = prevFrame + int(float(nextFrame - prevFrame)*elapsedRatio); servo.writeMicroseconds(map(interpolated,0,1000,servoMin,servoMax)); }
Función de actualización de servo
Esta función hace el código más limpio mediante la eliminación de una parte de él desde el bucle principal.
En primer lugar la relación del marco que ya se ha completado se calcula utilizando el número de milisegundos que han transcurrido desde que comenzó el marco, dividido por el número de milisegundos que se necesita para completar un marco.
Esta relación se pasa a lo largo de la función de interpolación para cada servo, actualizar posición de cada uno.
void updateServos() { float frameElapsedRatio = float(frameElapsed)/float(frameDuration); writeInterpolatMicros(LS,LSlast,LSnext,LSMin,LSMax,frameElapsedRatio); writeInterpolatMicros(LE,LElast,LEnext,LEMin,LEMax,frameElapsedRatio); writeInterpolatMicros(RS,RSlast,RSnext,RSMin,RSMax,frameElapsedRatio); writeInterpolatMicros(RE,RElast,REnext,REMin,REMax,frameElapsedRatio); writeInterpolatMicros(LN,LNlast,LNnext,LNMin,LNMax,frameElapsedRatio); writeInterpolatMicros(RN,RNlast,RNnext,RNMin,RNMax,frameElapsedRatio); }
Bucle principal
El principal loop() es donde sucede toda la acción, tan pronto como termine de ejecutar todo el código contenido dentro de ella salta hacia atrás al principio y comienza todo otra vez.
El primer paso en el bucle principal es para registrar el número de milisegundos desde que empezó a correr el programa) por lo que podemos determinar cuánto tiempo ha transcurrido desde la última iteración del bucle.
Con este tiempo podemos determinar si el tiempo transcurrido es mayor que el período definido para la interpolación pasos, si es así, llaman a la función updateServos() para generar nuevas posiciones interpoladas.
También comprobamos si el tiempo transcurrido es mayor que la duración del cuadro, en cuyo caso tenemos que llamar a la función setNextFrame().
void loop() { unsigned long currentMillis = millis(); if(currentMillis - previousInterpolationMillis > interpolationDuration) { // save the last time that we updated the servos previousInterpolationMillis = currentMillis; //increment the frame elapsed coutner frameElapsed += interpolationDuration; //update the servos updateServos(); } if(currentMillis - previousFrameMillis > frameDuration) { // save the last time that we updated the servos previousFrameMillis = currentMillis; //reset elapsed frame tiem to 0 frameElapsed = 0; //update the servos setNextFrame(); }