Recent Posts

Friday, January 15, 2010

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:

    if( gFrameIdx == gNumOfFrame )
{   // are we at the end of the scene ?
           gFrameIdx = 0;
        F_PLAYING=0;                        // clear F_PLAYING state
        TIMSK &= 0xfb;                      // Timer1 Overflow Interrupt disable
    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
    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]=16;  // This is the (last ID - 1) why is it hardcoded ?

    CheckSum = 0;
        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;

    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.

No comments:

Post a Comment