Stap 20: Code: besturingselement toe te voegen
In de vorige stap we een fundamentele crawl routine gedefinieerd en besproken van het gebruik van interpolatie te bewegen soepel de servo's. Nu, wat als u wilt controleren uw Zombot?
Er zijn oodles van manieren om dit, zowel in de hardware en de software te implementeren. Ik koos om te behandelen met behulp van de Arduino seriële verbinding, wat het zal echt triviaal betekent voor controle van de robot draadloos via Bluetooth, of zoals ik doe op dit moment voor foutopsporing, eenvoudig via de USB-kabel.
Dit is in geen geval de meest elegante manier om deze resultaten te bereiken, maar het is hopelijk gemakkelijk te lezen en te begrijpen, en flexibel.
Als voordat ik de volledige code hieronder zal hechten, maar ik zal gaan door de belangrijkste punten hier.
Communicatieprotocol
Ik heb besloten mijn communicatieprotocol instellen op de volgende manier.
- Elk gegevenspakket (instructie) van de controller naar de robot is een tekenreeks die bestaat uit twee delen, gescheiden door een ":" karakter.
- De "command" voorop en de robot vertelt wat te doen (bijvoorbeeld: beginnen met het verplaatsen)
- De 'argument' komt tweede en extra informatie
- Elk gegevenspakket begint met een "[" en eindigt met een "]"
Globale variabelen definiëren
Naast de eerder gedefinieerde variabelen moeten we nu om setup sommige variabelen die worden gebruikt in onze communicatieprotocol via seriële.
char inDataCommand[10]; //array to store command char inDataArg[10]; //array to store command argument int inDataIndex = 0; //used when stepping through characters of the packet boolean packetStarted = false; //have we started receiving a data packet (got a "[" boolean argStarted = false; //have we received a ":" and started receiving the arg boolean packetComplete = false; //did we receive a "]" boolean packetArg = false; //are we reading the arg yet, or still the command
Functies definiëren
Lezen van een opdracht van Serial
Deze functie kan worden aangeroepen om te controleren de seriële interface voor ontvangen opdrachten.
Het controleert de inkomende seriële bytes (tekens) één voor één, gooien ze weg, tenzij zij een "[" die geeft het begin van een opdracht.
Zodra een opdracht is gestart, elke byte wordt opgeslagen in de variabele "command" tot een ":" of "]" wordt ontvangen. Als een ":" is ontvangen, we beginnen met het opslaan van de volgende bytes in de variabele 'argument' tot een "]" wordt ontvangen.
Als op om het even welk punt een "[" is ontvangen tijdens de lezing van een andere instructie, dat de vorige instructie is verwijderd. Hiermee voorkomt u dat ons vast komen te zitten als iemand nooit verzonden een "]" einde-van-command teken en we wilden een nieuwe opdracht.
Na ontvangst van een volledige opdracht heeft de "processCommand"-functie wordt aangeroepen, die eigenlijk interperet zal en actie de opdracht.
void SerialReadCommand() { /* This function checks the serial interface for incoming commands. It expects the commands to be of the form "[command:arg]" where 'command' and 'arg' are strings of bytes separated by the character ':' and encapsulated by the chars '[' and ']' */ if (Serial.available() > 0) { char inByte = Serial.read();; //incoming byte from serial if (inByte == '[') { packetStarted = true; packetArg = false; inDataIndex = 0; inDataCommand[inDataIndex] = '\0'; //last character in a string must be a null terminator inDataArg[inDataIndex] = '\0'; //last character in a string must be a null terminator } else if (inByte == ']') { packetComplete = true; } else if (inByte == ':') { argStarted = true; inDataIndex = 0; } else if (packetStarted && !argStarted) { inDataCommand[inDataIndex] = inByte; inDataIndex++; inDataCommand[inDataIndex] = '\0'; } else if (packetStarted && argStarted) { inDataArg[inDataIndex] = inByte; inDataIndex++; inDataArg[inDataIndex] = '\0'; } if (packetStarted && packetComplete) { //try and split the packet into command and arg Serial.print("command received: "); Serial.println(inDataCommand); Serial.print("arg received: "); Serial.println(inDataArg); //apply input processUserInput(); packetStarted = false; packetComplete = false; argStarted = false; packetArg = false; } else if (packetComplete) { //this packet was never started packetStarted = false; packetComplete = false; argStarted = false; packetArg = false; } } }
Verwerken van een opdracht
Zodra een geldige opdracht (en optioneel een argument) zijn ontvangen, moeten zij worden geteld, zodat we de nodige maatregelen kan treffen.
Voor het moment heb ik niet meer dan één byte van informatie aan het definiëren van een opdracht, zodat we alleen kijken naar de eerste byte van de opdracht vereist. Met behulp van deze byte als het argument voor een switch() verklaring toe: staan ons voor het uitvoeren van een functie gedefinieerd door de byte van de opdracht.
In dit voorbeeld zijn we op zoek naar de teken "w", "s" of "c".
Als "w" wordt ontvangen, dan is de animatieframes worden overschreven met een nieuwe animatie waarin een "butterfly"-beweging.
Als "s" wordt ontvangen, dan is de animatieframes worden overschreven met een nieuwe animatie waarin een "crawl" beweging.
Als "c" wordt ontvangen, dan is de animatieframes zijn allemaal ingesteld op dezelfde positie, effectief stoppen alle verkeer.
Aangezien men niet alle waarden in een array tegelijk opnieuw toewijzen, we eerst definiëren een nieuwe tijdelijke array voor elke servo, met de nieuwe frames, gebruik dan de "memcpy" om de werkelijke frame matrix locatie in het geheugen worden deze waarden overschreven.
void processUserInput() { /*for now all commands are single chars (one byte), can expand later if required char commandByte = inDataCommand[0]; if (commandByte != '\0') { switch (commandByte) { case 'w': { //synchronised forwards (butterfly) numFrames = 4; int newRS[] = {0,1000,1000,0}; int newRE[] = {0,0,1000,1000}; int newLE[] = {0,0,1000,1000}; int newLS[] = {0,1000,1000,0}; int newLN[] = {1000,1000,0,1000}; int newRN[] = {0,1000,1000,1000}; memcpy(crawlGaitRS, newRS,numFrames); memcpy(crawlGaitRE, newRE,numFrames); memcpy(crawlGaitLE, newLE,numFrames); memcpy(crawlGaitLS, newLS,numFrames); memcpy(crawlGaitLN, newLN,numFrames); memcpy(crawlGaitRN, newRN,numFrames); break; } case 's': { //crawl stroke, 180 degrees out of phase numFrames = 4; int newRS[] = {0,1000,1000,0}; int newRE[] = {0,0,1000,1000}; int newLE[] = {1000,0,0,1000}; int newLS[] = {0,0,1000,1000}; int newLN[] = {1000,1000,0,1000}; int newRN[] = {0,1000,1000,1000}; memcpy(crawlGaitRS, newRS,numFrames); memcpy(crawlGaitRE, newRE,numFrames); memcpy(crawlGaitLE, newLE,numFrames); memcpy(crawlGaitLS, newLS,numFrames); memcpy(crawlGaitLN, newLN,numFrames); memcpy(crawlGaitRN, newRN,numFrames); break; } case 'c': { //turn left crawlGaitRS[] = {250,250,250,250}; crawlGaitRE[] = {250,250,250,250}; crawlGaitLE[] = {250,250,250,250}; crawlGaitLS[] = {250,250,250,250}; crawlGaitLN[] = {250,250,250,250}; crawlGaitRN[] = {250,250,250,250}; break; } } } }
Hoofdlus
De enige toevoeging vereist in de hoofdlus is een oproep om te controleren of de invoer van de gebruiker op elke iteratie van de lus.
//get user input using the SerialReadCommand function we wrote SerialReadCommand();