Followers

Recent Posts

Friday, January 29, 2010

Come with me - if you want to live.

I finally watched Terminator Salvation the other night.

I couldn't work out if the machines actually knew who Kyle Reese was or why they wanted to capture him - and also why they didn't just kill him when they had the chance!

There was also the matter of how lightly guarded skynet central was (despite apparently producing about 1 T800 per second), and how those motorcycle robots were supposed to get up if someone knocked them over.

What I really wanted to talk about though, was the Robopocalypse. Not so much the idea that robots will eventually rise up against their human masters, but just about how robots (or any machine) can be dangerous if people are careless around them.

I used to be vice-president of the Sydney University Mechatronics Organization, which held a yearly robot wars competition. Now, as you can imagine, robots designed to tear each other to bits would be equally (more?) effective at maiming pathetic squishy humans, so it was hard to disregard OH&S.

Despite a strong emphasis on safety, the university was in a difficult position when it came to allowing students to use their facilities. If we were injured in one of the workshops or laboratories, then the uni was liable - even if we were being properly supervised or if it was due to our own negligence. However, if we attempted to work on the robot outside of the university, we were without the appropriate tools and safety equipment and were significantly more likely to injure ourselves. This created many headaches and administrative woes, which ultimatley turned what should have been a fun and simple activity for students into a legislative nightmare.

This is why I am so much in favor of the smaller and safer robo-one style competition. I think it is a more elegant form of robot combat, for a more civilized age.





This video is from a new humanoid competition called RT Corp under 1kg Robot Fight. Like robo-one, it emphasizes lightweight robots which wrestle each other in a ring.

I think that this is a better direction for schools and universities who want to encourage students to learn about robots and mechatronics while having a bit of fun.

  1. Robot wars robots are glorified remote control cars. There, I said it. Many designs, like wedges or saws don't need any additional circuitry to control their weapons. The design is primarily in the mechanical field, and students will learn very little about the electrical or software side.
  2. It's so much safer. There are no dangerous weapons, and the weight limit is also much lower (unless you're these guys). The machining process is also much simpler (use tin-snips instead of table saws, solder instead of welding, etc).
  3. You get to keep your robot at the end. One of the most annoying things about robot wars is that if you lost, your robot was toast. Nothing gets damaged or destroyed in a wrestling match, so you are free to upgrade and modify parts for next time.
  4. The emphasis is on design and technique, not raw power. Software, real-time control, electrical systems and weight distribution are just as important as how strong your motors are.
  5. It's just as exciting. Robots can take their inspiration from boxing, tae-kwon do, ju-jitsu, MMA, akido, pancrase or any other type of martial art.

    The biggest drawback is that it can be quite expensive to build these little guys, but probably not by a huge margin. Our 13kg Robot wars Robots cost $300-500 (excluding the remote controls, which were borrowed), but I can see a under 1kg robot being build for a similar amount.

    If that doesn' convince you, just look at how much fun these guys are having!



    Wednesday, January 27, 2010

    It's not a bug, it's a feature

    Mystery solved!

    Here is a quote from the avrlibc documentation (which I probably should have looked at earlier):

    In order for these functions to work as intended, compiler optimizations must be enabled, and the delay time must be an expression that is a known constant at compile-time. If these requirements are not met, the resulting delay will be much longer (and basically unpredictable), and applications that otherwise do not use floating-point calculations will experience severe code bloat by the floating-point library routines linked into the application.

    This describes exactly the symptoms I was experiencing - if the function was passed a constant, it worked flawlessly. If I was using a value fetched from program memory, then the compiler was unable to determine how many NOPs should be performed and the delay was unpredictable.

    I don't really know why the library function requires prior knowledge of how long a delay should last. All a spin loop does is keep the processor busy for a set number of cycles. If you need longer, you can simply put another loop around the first loop to multiply it's effects. Sure, without careful calculations you can lose your nanosecond accuracy, but when you are working in the millisecond range it is usually unimportant if you are off by a few clock cycles.

    If you want the best accuracy, you should be using a timer or counter interrupt anyway...

    ..but that's a story for another night. I don't intend to be using spin loops for future versions anyway.

    Thanks to the people who emailed me and suggested that I might be accidentally passing the value of the pointer to the function, instead of the dereferenced value. That would also explain the strange behaviour, but it would also have shown up in the compiler output.

    In other news - what's up with the horrible popup?

    If you don't live in Australia (or perhaps even if you do) you might be unaware that the Australian government is considering introducing mandatory filtering of internet sites.

    Much like draconian airport security, national identity cards and racial profiling, this filter is supposed to be being put in place for our "protection". Because apparently Australians can't be trusted with unregulated free speech and freedom of information.

    I understand that some people are willing to sacrifice their personal liberties for the comforting feeling of saftey, but the problem is not with the filter as it exists now, but what it could become.

    First they came for the communists, and I did not speak out—because I was not a communist;
    Then they came for the trade unionists, and I did not speak out—because I was not a trade unionist;
    Then they came for the Jews, and I did not speak out—because I was not a Jew;
    Then they came for me—and there was no one left to speak out.

    Monday, January 25, 2010

    Byte by byte

    Here are a few things which might aid you in your detective work.

    First of all, I am still using avr-gcc and avrlibc to build the code. To achieve this, you first need to:

    compile,
    avr-gcc -mmcu=atmega128 -I. -g -Wall -Os -c *.c

    link,
    avr-gcc -o main.elf -mmcu=atmega128 *.o

    and then pack
    avr-objcopy -j .text -O ihex main.elf main.hex

    Then download the resulting .hex file using the RBCUpgradeTool. Note that you can simply switch the RBC block off and on, rather than messing around with the reset pin.

    This should help anyone who is trying to get to the bottom of the mystery I spoke about earlier. I also promised to talk a little about transferring data from the program space into working memory.

    Unlike desktop computers, embedded systems are built according to the Harvard Architecture which keeps the program instructions separate from the working memory. They even have separate buses, which means that an opcode and an operand can be fetched on a single clock cycle. In normal operation, there is no need to exchange data between the two areas of memory, since self modifying code has been all but banished to the history books.

    Sometimes, you find yourself running out of working memory especially if you are storing large tables or arrays which will eat up the free addresses very quickly. Also beware of memory leaks. Unlike a desktop with an operating system, your program won't segfault or blue-screen to let you know something is wrong - it will probably just start suddenly spewing garbage.

    However, if you absolutley must use giant arrays or matricies in your program, you can keep your RAM free by storing them in the program memory and then copy it out only when you need to use it. This is done by using the LMP (LoadProgramMemory) instruction, which enables a special register which shifts the data from the program bus onto the data bus.

    Unfortunatley, because the data bus is still only 8 bits wide, memory has to be loaded one byte at a time - so ints (which are 16 bits) need special routines to make sure that the high and low bytes are stored in adjacent locations. Also, reading a byte from flash memory (where the program is stored) takes more than a few clock cycles, during which execution needs to halt. Reading a large table could take up to a second.

    Most of the details and heavy lifting are handled by special hardware and instructions on the microcontroller, and core data types like floats have library functions in avrlibc. It is still the responsibility of the programmer to know when to load a table, and particularly to make sure that any memory loaded goes out of scope or is free()d when the operation is complete.

    The canned actions which are made using the robobuilder software suite can be exported as 16xN tables, where N is the number of poses (there are 16 servos for the basic robobuilder). For a complicated action, this could be many, many bytes - so the tables are necessarily stored in the flash memory. And of course, since only one action can be performed at a time, you can store dozens of actions in the 128kB of flash and simply swap them out when one needs to be performed.

    Friday, January 22, 2010

    A puzzle

    Boy do I have a good one for you today!

    First off, version 0.2 pre-pre-alpha-alpha-etc is available. Please download it and try it out - report any bugs or suggestions in the comments.

    The improvements in this update focus on enabling the execution of pre-generated actions, such as those from the robobuilder site. A few are included in the Hunobasic.h header for demonstration purposes. I have also written a few useful functions to transfer data from program memory to working memory (which will probably be the subject of the next update).

    In keeping with the philosophy of not trying to rewrite the default firmware, I've broken the procedure for executing an action (which is a series of poses) into much smaller steps, which makes it much more intuitive.

    Here is the method of making the robot walk forwards:

            getPGMWordArray((int*)HunoBasic_WalkForward_TrTime, (int*)delay, HUNOBASIC_WALKFORWARD_NUM_ACTION);
    //Load transition times

            for(i = 0; i <= HUNOBASIC_WALKFORWARD_NUM_ACTION; i++)
            {
    //For every pose in the action,
                getPGMByteArray((char*)HunoBasic_WalkForward_Position[i],(char*)position,16);
    //Load the angle for each of the servos
                setPose(position,torque,16);
    //Tell each servo to move
                if(i < (HUNOBASIC_WALKFORWARD_NUM_ACTION))
                {
    //After each frame, except the last,
                    j = delay[i];
                    while(j > 0){_delay_ms(1);j--;}

    //Pause for the required transition time.... 
    //wait... 
    //..WTF??
                }
            }

    What's up with that last part? Why would you loop ten times and each time delay for 1ms, when you could just delay for 10ms in one go?

    Well, here's why - it doesn't work!

    Substituting the line with:

    _delay_ms(delay[i]);

    will pause for a much longer time than it should. If you have a AVR, please test this out for me and confirm it. However, passing a constant value to _delay_ms() works exactly as intended. I've tried it many ways, but this is the only method which works so far.

    _delay_ms(25);
    works, but
    delay[i] = 25; 
    _delay_ms(delay[i]);
    does not.

    Mysterious.

    As I've mentioned before, this solution is hacky and unattractive. But I thought I might upload the code (the rest of which is working fine) and see if anyone has any suggestions as to why this might be.

    Friday, January 15, 2010

    One step back

    That wasn't so hard.

    I think that this code will be more useful to anyone who is trying to use the robobuilder for educational purposes. My goal is not to rewrite the official firmware, but to let programmers have access to the functionality of the RBC and the wCKs.

    Code can be found here. It is in pre-pre-alpha mode at the moment, so use it at your own risk. You might want to read this before you test it out.

    Bear necessities

    I was recently stumbl'dupon - which was appreciated. I'm glad to know that people out there are finding my work useful.

    Along with the increased traffic came several requests for the source code for some of my more interesting achievements.

    I am, of course, more than happy to provide it - you may have noticed that source code is one of those things which I often promise but seldom deliver.

    However, there is a good reason for my apparent reluctance to upload my work as of late, and it can be fairly accurately summarised in two words: self confidence.

    For some reason, programmers have an ingrained tendency to look at any code written by someone other than themselves and immediately write it off as garbage.

    In some cases, this is probably just good old fashioned hubris. But my current working theory is that in programming there are so many levels of mastery and so many potential solutions to any problem. Code using paradigms which are advanced and unfamiliar seem needlessly complicated, while basic (but probably equally effective) methods of performing a task are often written off as trivial and inelegant.

    So I tried to be understanding. Sure, I might not have done it that particular way myself, but with a positive mindset I could see the logic behind most of the design decisions.

    But try as I might, I kept having to resort to ugly hacks to get the code to do what I wanted while still maintaining it's original functionality. After awhile, I had to sacrifice the default behaviour altogether. Eventually, I found myself rewriting huge sections of code in order to make even the most basic functions work reliably. But I figured that I had come this far with the official code, and it would just take me too long to find an alternative.

    Until I saw this:

    ISR(TIMER1_OVF_vect) 
    {
        if( gFrameIdx == gNumOfFrame )
    {   // are we at the end of the scene ?
               gFrameIdx = 0;
            RUN_LED1_OFF;
            F_PLAYING=0;                        // clear F_PLAYING state
            TIMSK &= 0xfb;                      // Timer1 Overflow Interrupt disable
            TCCR1B=0x00;
            return;
        }
        TCNT1=TxInterval;
        TIFR |= 0x04;                            // restart timer
        TIMSK |= 0x04;                            // Timer1 Overflow Interrupt enable
        MakeFrame();                            // build the wCK frame
        SendFrame();                            // send the wCK frame
    }

    void MakeFrame(void)
    {
        while(gTx0Cnt);            // wait until the transmit buffer is empty
        gFrameIdx++;            // next frame
        SyncPosSend();            // build new frame
    }


    //------------------------------------------------------------------------------
    // Start sending the frame
    //------------------------------------------------------------------------------
    void SendFrame(void)
    {
        if(gTx0Cnt==0)    return;    // return if no frame to send
        gTx0BufIdx++;
        sciTx0Data(gTx0Buf[gTx0BufIdx-1]);        // send first byte to start frame send
    }

    void SyncPosSend(void) 
    {
        int lwtmp;
        BYTE CheckSum; 
        BYTE i, tmp, Data;

        Data = (Scene.wCK[0].Torq<<5) | 31; // get the torque for the scene

        gTx0Buf[gTx0Cnt]=HEADER;
        gTx0Cnt++;       

        gTx0Buf[gTx0Cnt]=Data;
        gTx0Cnt++;       

        gTx0Buf[gTx0Cnt]=16;  // This is the (last ID - 1) why is it hardcoded ?
        gTx0Cnt++;       

        CheckSum = 0;
        for(i=0;i
            if(Scene.wCK[i].Exist){    // if wCK exists add the interpolation step
                lwtmp = (int)Scene.wCK[i].SPos + (int)((float)gFrameIdx*gUnitD[i]);
                if(lwtmp>254)        lwtmp = 254; // bound result 1 to 254
                else if(lwtmp<1)    lwtmp = 1;
                tmp = (BYTE)lwtmp;
                gTx0Buf[gTx0Cnt] = tmp;
                gTx0Cnt++;            // put into transmit buffer
                CheckSum = CheckSum^tmp;
            }
        }
        CheckSum = CheckSum & 0x7f;

        gTx0Buf[gTx0Cnt]=CheckSum;
        gTx0Cnt++;            // put into transmit buffer




    That was probably a bit on the long side. But here's a quick tip for programmers working on embedded systems - if it's too long to post on your blog, it's too long to put in AN INTERRUPT!

    Interrupts need to be short. When an interrupt fires, everything else is put on hold until the RETI (return from interrupt) instruction is performed. If your code spends too long in an interrupt, you risk missing other important events such as sending instructions to servos, receiving serial transmissions, or responding to any other interrupt.

    What's worse: since all these events have been put on hold when the RETI instruction actually is received, all these issues now have to be dealt with at once. It's not easy to predict what order they will be handled in either. This can lead to unpredictable behaviour which is very hard to debug.

    So to summarise: bloated interrupts = bad.

    To return to my original point, I had been doing some very dodgy things in order to just get the darn thing to work. Things like arbitrary delays in the middle of functions (to make sure that whatever interrupt was expected had time to get itself sorted out), and lots of polling to ensure that the state of the program was predictable before I went and altered the global variables (of which there were many). Plus a not insignificant amount of arcane deep magic.

    Not the sort of work I wanted to put my name on.

    All that stuff I said about respecting other people's code remains valid, but only if it's actually doing what it is supposed to. So after a short meditation, I realised something very important: I didn't need all this stuff. 

    I had a very specific goal in mind, and 90% of the code I was having to circumvent was simply getting in the way. I only needed a few basic functions to achieve what I needed to do - and at this stage I was spending far too much time patching things up instead of fixing the problem at the source.

    With this in mind, I've begun working on a very simple library of functions which will deal with the simple, low level things that I am trying to achieve - without all the non-essential tasks getting in the way. With any luck, I will be able to make far more frequent source code updates now, since the releases will be far more minimalist and focused on a specific task - instead of trying to be a replacement for the default firmware.

    Until next time - keep it simple.

    Sunday, January 10, 2010

    A robot by any other name

    I've been a bit serious recently, so lets have some fun.

    First off, I've realised that my robot is more than a month old now, but still has no name! I'm not good with names, so I thought I'd open it up to the floor and see if anyone had any suggestions.

    If possible, it should be a name which conveys his gentle nature, effervescent personality and love of outdoor sports.

    Next, it's time for the cool robot of the week!



    I've been talking about control theory quite a bit recently, and I've been doing some research into different methods which might be applicable to my dynamic balance problem. This video is of an inverted pendulum - the arm is free to rotate on the cart, and only the cart is free to move along the x-axis. The cart has a model of the system which it uses to swing the pendulum into a stable inverted state. This is a well studied area of control theory, and today's modern robots (and humans) have been doing this for awhile.

    Not impressed? What about this:




    Same problem, but with an extra degree of freedom. Now there is a compound pendulum (one pendulum free to rotate off the first pendulum), but still only the one cart. This is a non-holonomic system, which is much harder to control using conventional control methods.

    Still not enough?





    Here is the double pendulum problem again, but this time the cart has been replaced by an arm. Instead of being constrained to two dimensions, the control algorithm now has to cope with the non-linearity of moving the control arm in a 3D rotational space.

    I know it's not the cute little humanoids you are used to, but from an engineering perspective this is significantly more impressive watching robots dancing.

    Ok, maybe not.

    Friday, January 8, 2010

    Robots are our friends

    A while ago, I watched a news story about military robots. The tone was factual, but their "expert", so to speak, came off as a little bit alarmist (pro tip: remember to include more than one expert if you're trying to come off as unbiased).

    I can understand that to someone unfamiliar with computers and robots might see something like a Predator and begin to feel uncomfortable. People always fear what they don't understand, so if a person believes that their car is trying to kill them or their computer is alive - surely anyone should be able to see that arming robots to the teeth is a bad thing (TM).

    The bottom line is though, that despite the layers of mechanical and digital abstraction that make up the robot, there is always a human at the other end who pulls the trigger.

    Today's military robots are really nothing more than glorified remote control cars with cameras attached. If only robots were as advanced as movies and the media make them out to be. They have degrees of autonomy, such as navigating obstacles or maintaining a fixed altitude flight path, but when it comes to the grizzly business of actually fighting, robots will always surrender control to an individual who is better suited to the task.

    The reason for this is simple - robot's can't interpret the world in the same way that people do. Sure - they can "see" with 5 megapixel camera, and "hear" with microphones, but all this data still needs to be interpereted in order for the robot to understand what the images and waveforms mean. The science of interpreting data into meaningful information is known as perception.

    Perception remains one of the hardest and least solved problems in robotics. There are videos of prototype robots like ASIMO who can see an object and remember what it is, but the robot is still only parroting what it has been told. Artificial intelligence still lacks the ability to process semantic information about it's environment.

    As humans, we have tens of years of experience which we can draw upon to derive the meaning of events going on around us. When confronted with an alien situation, we can look back into our vast database of knowledge and see if any of the information we have collected is relevant to understanding what is going on. Sometimes it's hard. Even humans make mistakes. And unless the robot has access to a human-like reservioir of knowledge about how to differentiate between a friendly and enemy soldier or a soldier who is surrendering or a Sharnaff bull, the risks of friendly fire are simply too great. And the engineers who build robots understand this.

    So if a robot can't correctly interpret the environment around it, it becomes no more useful than a land mine.

    A very complicated, fragile and above all expensive land mine.

    If an army wants to indiscriminately kill civilians, non-combat personnel and soliders from both sides, then there are much more efficient methods of achieving it.

    But, in the hands of an intelligent operator, the robot becomes an effective battlefield participant who is far more expendable than your average squishy human - but just as much of a threat.

    From this perspective, you can see how military robots actually save lives. There are simply less humans exposed to danger. Nobody is going to get upset if a robot gets shot to pieces (except maybe the accountants).

    I think I should make it clear that I am not pro-war: just pro-robot. We've already tried the land mine thing, and it failed. We don't need another generation of weapons which cannot discriminate between a soldier and a child. But if the robots are being used responsibly to keep humans out of dangerous situations, then it might be a step forward.

    Wednesday, January 6, 2010

    Form vs Function

    I thought you might appreciate this video of my robot falling over.



    At the end, you may notice that his legs get stuck together like an imperial walker during the battle of Hoth! Ah, memories.

    The instructions in the manual indicate very clearly that the nuts should be placed on the inside of the leg - presumably for aesthetic reasons. However, the short section of bolt which sticks out of the inside has a nasty habit of getting hooked on the opposite foot.

    If you can tolerate your robot looking a bit road warrior themed, I suggest reversing the direction of the bolts to avoid having your robot flail about like a memermaid, and also the internal damage to the plastic casing:





    I do often complain about the robobuilder design, but I think I should make it clear that I am by no means unhappy with it. These are small complaints which (I hope) will be addressed by the robobuilder community and also allow others to avoid my mistakes.

    The wCK servos are actually the best robotics servos I've used for their price. You can skip the durability issues I've been having, simply by getting the wCK 1111 version, which has 3 metal gears and a higher torque! They have a suite of extra features, such as brake mode, 10bit positioning (360/1024 = 0.35 degree accuracy!), runtime adjustable PID settings, continuous rotation mode, and the ability to write simple programs to run on the servos themselves.

    I have yet to use these features, but I can already think of a thousand uses for them.

    Tuesday, January 5, 2010

    One step at a time

    Happy New Year Everyone!!

    I'm back from a short holiday down to the Melbourne Cricket Ground to watch the Boxing Day test. It was very relaxing, but now that I have returned I have lots of robot news to share!

    Firstly, despite being unable to find wCK gears anywhere in the southern hemisphere, I have sort of fixed the broken knee servo. Thanks to an emergency surgical field graft, he can now walk fairly normally but has lost the use of his right arm. Apparently I'm not the only person who is having problems with the plastic gears. If you are concerned about damaging your own servos, I suggest you set them to the lowest torque setting. Note that 4 is the LOWEST torque setting, and 0 is the HIGHEST - the manual could probably be more forthcoming on this point.

    Now that he's up and mobile again, I've resumed work on getting him to keep his balance while standing.

    Whenever I start work on an ambitious project, I find that the only way to keep on track is set myself small targets along the way. I am always coming up with ideas that are easy enough to design, but take far longer to implement - and more than a few of them have fallen by the wayside because my eyes were bigger than my stomach.

    So having small, short term achievements keeps me motivated to move on with the long term goal. A very simple method of controlling complex systems (particularly when computing power is limited) is called discrete state-space control. Rather than trying to model the system and predict how it will behave, the system is manually broken down into a discrete set of inputs and outputs. Then, using some very basic rules, the behaviour of the system can be described in very simple terms like: if A, then B.

    In the case of the robot trying to maintain it's upright position, you can simplify things by firstly holding most of the joints rigid. Using the feedback information from the ankle servos (04 and 00), you can predict if the robot is tilting too far to the left or right. Then, depending on which state the robot is in, it can take the appropriate action to regain it's balance. Simple, no?



    In the video, you can see the robot measuring the angular velocity of it's ankles, and detecting when it's balance is being compromised. It simply steps away from the force to maintain it's stability - just like a human would.

    Right now, it only work for left and right - simply because the canned robobuilder animations for forwards and backwards consist of several steps, meaning I will have to write my own motions.

    I should probably start putting together my own library of poses anyway, since I'm not too happy with the default ones. The walking and turning motions have a real problem on many surfaces - polished wooden floors are the best so far, but far from perfect.



    Here you can see that the robot is having problems moving in the right direction with the stock SidewalkRight/Left actions, to the extend that he goes the wrong way! Because he was off centre to begin with, one foot is carrying most of his weight, so he pushes against the ground and towards my hand.

    Code will be available soon, once I've cleaned it up a little and found a nice place to upload it. I think that robosavvy has personal file space for it's members, so I will see if I can make the files public.