Tutorial: Arduino/Computer controlled robot

  1. wireframewolf wireframewolf is offline Transistor
    Category
    Introduction
    Views
    1,102,606
    Replies
    21
     

    Arduino/Computer controlled robot

    Difficulty
    Pretty easy
    Estimated Time
    A couple hours
    Skills Required
    Basic C programming knowledge
    Arduino programming knowledge
    Previous experience with file I/O is a big plus
    Parts Required
    -An Arduino
    -A computer with Linux on it
    -A USB cable to connect them
    -Your robot
    Tools Required
    Fingers for typing, mostly
    This article is about how communicate between the Arduino and a Linux based PC via a C program, for use on a robot. It is not a tutorial on C on Arduino code, but I do try to give a good overview on serial communication itself.

    The basic idea is to have the Arduino send sensor data to the computer, which will do all the major processing. Based on this sensor data, the computer builds a data packet on how to move the robot's actuator's in response, and sends this data to the arduino, which controls the actuators themselves (directly or via a motor controller). The arduino then grabs sensor data again and the cycle begins anew.



    The advantage to having a full powered computer act as your robots brain, besides more processing power, is that it makes functions like networking, data storage, and programming much easier. You can easily output visuals and sound from a computer, and advanced input like cameras and microphones are also simpler to deal with.

    For reference along the way, you'll want to grab these two files: (Attachment 420) and (Attachment 419). Those are the C source for the PC and the code for the Arduino, respectively. It's important to note that my program is based off of the example from Tod K's blog, at this link here: http://todbot.com/blog/2006/12/06/arduino-serial-c-code-to-talk-to-arduino. He wrote several simple, reusable functions that I use in my program:

    serialport_init()* There is one minor change I had to make to this to make the program work on my system. The change is in the notes section.
    serialport_read_until()
    serialport_write()

    Rather than going into too much detail about how these work, I will just show you where and why you should use them in your own program. For all intents and purposes think of them as part of a useful library of functions you will want to keep for future use

    Serial Communication

    Okay, so quick overview of serial communication!

    We have two computer-like devices, A and B. They want to send some data to each other, so they first have to establish a serial connection. Aside from a physical connection, they have to call some connection function from their I/O libraries. The Arduino has Serial.begin(speed), which you've probably seen, and C uses a function called open(file, speed).

    Once a connection is established, one device can send an array of bytes to the other, with a write(data) function. This data gets send over your serial wire (or whatever medium you happen to be using) and gets stored in the second device's buffer. A buffer is space of limited size where the data sits until B pulls it out for processing, kind of like a mailbox. If too much data is sent and B doesn't handle it, the buffer can potentially overflow.

    So if device A sends the string of characters "Hello!" over to device B, they will get stored in B's buffer until B calls a read() function. This function takes one or more bytes out of the buffer and stores them into a specified variable or array of variables.

    The same situation applies to B sending data to A.




    Our Program


    Making this happen in the proper order for our robot can be a bit tricky, but whip out that source code, cause we're going to drive right in. Pretty much all the action happens in the main method of the the C program, and the loop() function in the Arduino.

    The basic layout is that when the C program and the Arduino first start, they declare some variables and initialize a serial connection. After that they have a main loop that keeps on going forever, trading information between the two devices and processing it.

    The initialization for the Arduino: Serial.begin(9600); //9600 is the baud rate, or speed of data transfer. Just leave it at 9600 and don't think about it too hard.

    For the computer, we use the first of Tod K's handy functions: int serialport_init(filename, 9600);

    This serialport_init function returns an integer that you're going to want to store, because it identifies which port connection you're talking about. To a Unix system, files, serialport connections, and internet ports are all pretty much the same thing... generic I/O data streams. When you connect your Arduino to your computer, a driver creates a file which represents that serial port connection. You can use the Arduino IDE to find out the name of that file. So my code looks like this:

    int fd = serialport_init("/dev/ttyUSB0", 9600);

    If fd isn't -1, then we have successfully initialized a serial connection!

    Okay, so now our Arduino and PC and have made eye contact and to avoid that awkward silence, one of them has to start the conversation. This is where the main loops start, and in my example, the computer sends data to the Arduino first. I did it this way because the Arduino is already connected and waiting when the C program starts, but doesn't know the program has started until it receives data from it. However, since the program doesn't have any sensory data to process yet, the first command it sends does not tell the Arduino to do anything.

    So the Arduino first waits for some data from the PC:

    Code:
     loop() {
         if(Serial.available()) {
         }
    }
    The computer has to send a data packet.

    Code:
      char actuatorData[10] = "0000:\0"; //previously declared data
     
    //main loop
    while(1) {
         int bytesSent = serialport_write(fd, actuatorData);
         if(bytesSent == -1) { fail(); }
    }
    Again we're using another one of Tom K's reusable methods to write the array actuatorData. Let's talk for a moment about what our data packet looks like.
    It is ten bytes, long, but there are only four characters 0, followed by a semicolon ':', and null terminator character \0.

    The first four bytes are the actuator data itself. For example, if we have a motor on our robot, we may set the first byte to 'f' if we want the motor to turn forward, or 'b' if we want the motor to turn backward, and keep it as '0' if we don't want it to do anything at all. The second byte might indicate the speed, on a scale of 0 to 255, that we want the motor to turn.
    The second two bytes could possible represent these same things for a second motor on our robot.

    The semicolon is punctuation. As I will soon show, this is so the receiver knows when it has the whole data packet, and that the sender is done sending data and waiting.

    The \0 is a null terminator character which you should put at the end of your strings in C, but not on the Arduino! It is only there so the serialport_write() functions knows when to stop writing. The null terminator character, and any bytes in your array after, will not be sent over the serial connection.

    So, the Arduino, when it sees that the computer is sending data, is going to have to grab that data, store it, and and process it. It is going to look like the following:

    Code:
     char dataFromComputer[20];
    
    loop() {
          if(Serial.available()) {
              char m;
              int dataCounter = 0;
              while((m = Serial.read()) != ':') {
                   if(m != -1) {
                        dataFromComputer[datacounter] = m;
                         ++dataCounter;
                   }
              }
    
              //process our data. If it is 'f' 220 '0' '0' for example, we might tell the motor controller to spin the right motor forward at a speed of 220/255
               //Collect sensor data
              //send response
         }
    }
    Now to explain. All this time the Arduino has been sitting idle, waiting. As soon as it sees that some data has been send to it (with Serial.read()) it goes into an inner loop. This loop keeps looking at the serial buffer.
    If Serial.read() returns -1, there is nothing in the buffer, and the Arduino does nothing. This is important because the Arduino can call read() faster than the computer can send data and from the time the computer sends the first byte, there will be times in between bytes where the Arduino's buffer is clear.
    If Serial.read() returns a ':', we know that we can exit our reading loop because the computer is done sending data.
    Anything else Serial.read() returns is data we want to process so we store it in a buffer.

    After processing this data we then want to get some values from our sensors to send back to the computer for processing. If you haven't used the analogue pins on the Arduino yet, here's a quick rundown:

    Let's say you have a photoresistor (a light sensor). It's a little component that will output 0 to 5 volts depending on the brightness of the light shining into it. If you have it hooked to analogue pin X on the arduino, you can say:

    int brightness = analogueRead(x);

    This will return a number between 0 and 1028 that is proportional to the voltage your photoresistor is outputting on a 0 to 5 volt scale. To convert it to an approximate single byte, you can do something like

    byte brightnessCompressed = (byte)(brightness/1028*255);


    With that said, let's imagine you have now have two byte values derived from to light sensors on your robot. You want to send these back to the computer because the computer has to decide how to move the motors based on these light values. Maybe the robot wants to get closer to the source of light, or maybe the robot needs to find the darkest place possible, under the misty mountains. Either way, we can construct a simple data packet to send, similar to the one we received.

    char sensorData[3] = "00:"


    Here, the first two bytes represent our two sensor values, and the last one is, again, punctuation. Our final arduino code snippet looks like this:

    Code:
     char dataFromComputer[20];
      char sensorData[3] = "00:"
    
    loop() {
         if(Serial.available()) {
              char m;
              int dataCounter = 0;
              while((m = Serial.read()) != ':') {
                   if(m != -1) {
                        dataFromComputer[datacounter] = m;
                        ++dataCounter;
                   }
              }
    
              processData(); //process our data. If it is 'f' 220 '0' '0' for example, we might tell the motor controller to spin the right motor forward at a speed of 220/255
              sensorData[0] = getSensorValueOne();
              sensorData[1] = getSensorValueTwo();
              Serial.write(sensorData);
         }
    }
    Okay! Our Arduino has received, processed, and sent! All this time, our Computer has been waiting for incoming data, which it will now grab with Tom K's serialport_read_until() function:

    Code:
      char actuatorData[10] = "0000:\0"; //previously declared data
      char sensorData[20];
    //main loop
    while(1) {
         int bytesSent = serialport_write(fd, actuatorData);
         if(bytesSent == -1) { fail(); }
         int bytesReceived = serialport_read_until(fd, sensorData, ':');
         //process sensorData, set actuatorData as necessary.
    }
    This function handles what we did in the Arduino code... reads bytes into an array until it encounters a semicolon, afterwhich is processes the data, decides how the actuators need to be set, and starts the loop over again, sending the data to the arduino!

    I know these don't look like much, but that's because you haven't processed any data yet! That's totally up to you, but at least now you have any idea on how to get it from one side to the other.

    Further Thoughts

    This might not be the best way to do things, and there are some immediate flaws.

    Firstly, each of your devices spends time waiting for the other device to finish processing. Unfortunately if you don't wait, you data can get out of hand and you buffers can overflow, hell breaks lose etc etc.

    What I would recommend for this kind of system is alternative threads in your C program that process intensive algorithms that might take a while, and only process reflexive data every single cycle.

    Secondly, each piece of data is only one byte, a value of 0 to 255. Unfortunately, your punctuation mark that you use to signify the end of the packet means that if some random numerical value before that matches the value of your punctuation, your data reading will end early, you'll probably crash trying to process what you'll have, and your buffer will have crap in it on the next cycle.

    Two possible ways around this are to look for a sequence of bytes that match, rather than just one. So three ':'s in a row may be very unlikely to occur in your data, which means you can look for three one after the other before determining that you are done reading.

    The other is to know exactly how many bytes you are sending, and only read that many. Unfortunately, this can cause some problems with the way reading works in C (which I won't get into here)

    Notes

    Okay, here's one big thing with the functions I grabbed from that example.

    On Linux, serialport_read_until was always returning -1 no matter what. To fix this, I had to look into the serialport_init() function. Inside there, the open() function takes three flags as arguments, one of which is O_NDELAY. I took it out, and serialport_read_until worked! On Linux anyway. On Mac OS X, it caused open() to hang. I have a vague inkling on why this is, but no idea how to get around it. And I didn't test it out on Windows because the only thing my Windows machine does is play WoW

    Lastly, I'm no expert on serial communication, or anything else for that matter. If you see some big flaws with any of my so called facts or methods, let me know!
    Attached Files


Page 1 of 3 123 LastLast
Replies to Tutorial: Arduino/Computer controlled robot
  1. Join Date
    Sep 2006
    Location
    Carol Stream, Illinois
    Posts
    1,695

    Re: Arduino/Computer controlled robot

    Sweet Tutorial!

    �In the long history of humankind (and animal kind, too) those who learned to collaborate and improvise most effectively have prevailed�
    - Charles Darwin
        

  2. Join Date
    Apr 2008
    Location
    Sacramento, CA, USA Area
    Posts
    5,341

    Re: Arduino/Computer controlled robot

    Wow! Thanks for bumping this, Alex! I don't know how I missed it. +rep from me!
    I Void Warranties�
        

  3. Join Date
    Dec 2007
    Location
    Portland, OR
    Posts
    3,198

    Re: Arduino/Computer controlled robot

    Great job on this!
        

  4. Re: Arduino/Computer controlled robot

    This is a nice tutorial.

    I've been pondering the prospect of getting an Arduino with the Proto Shield kit and using it as a peripheral processor to Hammer. If connecting via USB, how does the master (Hammer, PC, etc) know what port to use to talk to the Arduino slave? There will definitely be at least two other USB devices attached to my Hammer (Thumb drive and WiFi).

    I've not seen anyone write code by using the type in the call before.

    8-Dale
    I can handle complexity. It's the simple things that confound me.
    Do everything in moderation, ESPECIALLY, moderation..
    Sometimes the only way to win, is not to play.. -- Stephen Falken
        

  5. Join Date
    Apr 2008
    Location
    Sacramento, CA, USA Area
    Posts
    5,341

    Re: Arduino/Computer controlled robot

    The RS232 virtual drivers would need to run on the Hammer. I haven't seen source code for them, so I'm not sure it would in that instance.

    If it doesn't, you would need to use the second com port (TTL) on the Ard-e, not the USB.

    Beyond that, it's a JAVA-based IDE to write Wiring, or gcc and use the onboard loader.
    I Void Warranties�
        

  6. Re: Arduino/Computer controlled robot

    Quote Originally Posted by Adrenalynn View Post
    The RS232 virtual drivers would need to run on the Hammer. I haven't seen source code for them, so I'm not sure it would in that instance.
    I wouldn't necessarily have to use the same drivers used in this tutorial on Hammer though. You use what the given platform supports. Hammer does USB Host just fine

    Quote Originally Posted by Adrenalynn View Post
    If it doesn't, you would need to use the second com port (TTL) on the Ard-e, not the USB.
    I don't agree.

    Quote Originally Posted by Adrenalynn View Post
    Beyond that, it's a JAVA-based IDE to write Wiring, or gcc and use the onboard loader.
    I've been following Arduino development for sometime. I've even checked the IDE out under both Windows and Linux (my preferred environment, of course).

    8-Dale
    I can handle complexity. It's the simple things that confound me.
    Do everything in moderation, ESPECIALLY, moderation..
    Sometimes the only way to win, is not to play.. -- Stephen Falken
        

  7. Join Date
    Apr 2008
    Location
    Sacramento, CA, USA Area
    Posts
    5,341

    Re: Arduino/Computer controlled robot

    The Arduino doesn't do USB Slave just fine. It's an FT232 virtual com port. Doesn't the work involved seem excessive when there's a serial port sitting on pins 1 and 2, or when you could just skip the version with the FT232 and use the version with two RS232 ports?

    And if you're going to that trouble - why the Arduino with its wussy little ATMEGA 128 and not John's Axon with the ATMEGA 640 instead? All that trouble for a device with a bit more than a dozen interfaces and one free com port when you could be using something with four dozen plus I/O and three free com-ports plus the USB?

    The Arduino just doesn't seem nearly as useful on WALTER as does an AVR640...
    I Void Warranties�
        

  8. Re: Arduino/Computer controlled robot

    Thanks for the words of approval This was my first informative article ever, so I know it could be improved. As soon as I get my robot to a good resting point, I want to learn how to use USBAVR on an Atmega for a similar process to what is in this tutorial, and when I do, I'll write a new article on that.

    Basically what happens is that the USBAVR firmware turns your AVR chip into a direct USB device, so you just wire up a USB cable to it and let the magic happen! Okay, it's not quite that easy, but it should theoretically be a faster and cheaper alternative to the Arduino. Not to mention you can use any atmega chip you want. Of course, you throw the ease of the Arduino out the window and right onto the tracks of an oncoming train. And the wreckage will look exactly like avr C code.
        

  9. Join Date
    Apr 2008
    Location
    Sacramento, CA, USA Area
    Posts
    5,341

    Re: Arduino/Computer controlled robot

    I'll have to look at that, 'Wolf, Thanks! Be interested to see how it works on the Axon.
    I Void Warranties�
        

  10. Re: Arduino/Computer controlled robot

    Quote Originally Posted by Adrenalynn View Post
    The Arduino doesn't do USB Slave just fine. It's an FT232 virtual com port. Doesn't the work involved seem excessive when there's a serial port sitting on pins 1 and 2, or when you could just skip the version with the FT232 and use the version with two RS232 ports?
    Hammer only has two serial ports exposed. One is the console port and the second is unused at present. I'll connect an SSC-32 to the second serial port.

    Quote Originally Posted by Adrenalynn View Post
    And if you're going to that trouble - why the Arduino with its wussy little ATMEGA 128 and not John's Axon with the ATMEGA 640 instead?
    I don't have your resources. The Arduino USB is $35.00 versus $130 for the Axon. Of course I would rather have an Axon, but just can't afford it.

    8-Dale
    I can handle complexity. It's the simple things that confound me.
    Do everything in moderation, ESPECIALLY, moderation..
    Sometimes the only way to win, is not to play.. -- Stephen Falken
        

Closed Tutorial
Page 1 of 3 123 LastLast