My Projects‎ > ‎

Briefcase Controller [In Development]

NOTE: this project is still in the development stages, I am updating this page as I go.


INTRODUCTION:

    I know most of you have seen The Jackel and Law Abiding Citizen.  Both these movies featured kick-ass briefcase controllers for their robots.  So,  I decided to build one for my robot.


Figure 1: Briefcase Controller Test Build





TECHNICAL DETAILS:

Figure 2: Functional Block Diagram


    The system is fairly simple, the AVR controls everything.  The MAX7456 holds the video ram as well as the character memory.  The character memory can be changed, this saves us a lot programming memory in the working program because, once the costume character is written to the MAX7456 once it stays within the system.  john.geek.nz has has a tutorial on the MAX7456 including how to create costume characters.  I wrote a VB program called QMIG that creates the code that you need for the costume graphics (works with johns code).

    




HARDWARE:


The Case:

    The Case is an Intec Pro PSP case. I picked it up for around $10 on Boxing Day mainly because ... well I'm always picking up kewl cheap nicknack's that look like they have some potential to be turned into something wicked.



The Turret:

    The turret isn't as exactly the MX-20 however, it is the best that I can do on a hobbyist's budget.  The wireless webcam is a XXX which I got off ebay for $35 (including the receiver).  



Display:
    
    I got a 7" LCD TV for $45 (again ebay). $40 a MAX7456 Breakout Board in order to generate the HUD On Screen.  The TV is normally turned on by a switch on the side of the display.  This is not baddass enough for my liking so I rigged it to activate using a switch with a missile switch cover.  This lead to a problem however, because it did not work.  After a bit of playing around, I figured out that the ON/OFF selector switch required the ground to signal to pass between the common pin and the OFF side of the selector switch.  Because the switch only has a NO state I rigged it to a relay in order to turn it into a NO/NC switch (an old industrial trick).



Joystick:

Figure 3: Joystick (image 'liberated' from Ada Fruit)


    Hardware wise the joystick is just 2 pots, one for the X axis and one for the Y axis.  We could use the ADC in order to read the values,  a value of 0,0 is at the bottom right hand corner, and 255,255 is at the top right hand corner (when using 8bit resolution).  The middle point is around the halfway mark 128,128.  See Figure 7 below.  While the joystick itself is easy to use the software is quite complex.



Keypad:

Figure 4: Keypad pinout (simplified)


    The keypad comes form ... you guessed it Sparkfun.  The keypad is a simple grid, when we press a button we close a path between 2 pins.  So if we hold down the 3 key we will have a continues path between the blue (2 pin) and the red (5 pin).
  The outer 2 pins (green) are not connected to anything.  This is a problem because, as I said in my Digital Input Tutorial (shameless plug) we need a GND(0V) or a Vcc signal to detect. By using a pull-up resistor we will see a Vcc signal when we have an open wire so, now we need GND signal to detect when we have a closed path.  So lets put a GND signal on one of the column pins (pin 1, pin 3 or pin 5) this will allow us to read the state of the row pins (pin 2, pin 4, pin 6, and pin 7).  So if we activate pin 1 reading the columns will tell us if we have pressed (2,5,8 or 0).  We can't however, hook up a ground to all the columns, as doing so would not give us a signal on the column pins but would not allow us to know which pin is ON.  So what we need is a way to connect the GND signal to only 1 column at a time.
    
    So what we need to do is apply a GND signal to one of the row pins, then read the columns pins.  If we detect a LOW(GND) signal we will know that on of the buttons is pressed.  We then need to disconnect the GND from that column and hook the GND up to the next column pin.  The good news is that this is not that difficult, since we are using the pull-up we get see a HIGH(Vcc) signal whenever we have no path to GND (ie buttons are not pressed) so that means that if we put a HIGH(Vcc) signal onto any columns its inputs will be ignored.  

    But wait there is more. If we press 2 buttons at once and both buttons are on the same row we stand a chance of causing a short circuit (if one column is HIGH(Vcc) and the other is LOW(GND) ).  To solve this problem we simply need to add resistors onto the column pins.  Any resistor 5k and up will work great.

    Just like any mechanical switch/button the keypad suffers from the button bounce effect, so we do need to implement a de-bounce of some sort.  I used a software de-bounce while developing and found that it worked great



Xbee:

Figure 5: Xbee voltage Divider Network


    The Xbee is used for bi-directional serial communication between the Controller and the Robot.  Hardware wise the Xbee is easy to use.  The only trick is that the Xbee is a 3.3V device while my AVR project runs at 5V.  So I added voltage divider network to the TX pin on the AVR in order to drop the TX voltage to 3.3V.  Using 10k with a 15k drops keeps the current down to 2mA which is safe for both devices.  I know that a lot of people say that you can safely run the Xbee on 5V (and it does work) but it will damage the Xbee over time.  There is a lot of discussion all over the net (Sparkfun, Adafruit, AVR Freeks ... etc) but according to the Digi Knowledge Base its not 5V tolerent.    



Battery:

Figure 6: LiPo Batteries (Image Liberated From Sparkfun)


    The project is powered by 2x Single Cell 850mA batteries.  LiPo batteries require a special charger BUT they are amazing for maintaining their power levels for a long time when not used and can output a lot of current.  

    The label is a bit misleading,  It says 3.7V.  In reality the Battery outputs 4.2V when it is fully charged and drops as low as 2.75V when it is fully discharged,  But realistically it commonly outputs 3.7-3.9V.  Discharging a LiPo battery below 2.75V can damage to unit, so make sure that you monitor the battery voltage levels using the ADC.  The minimum voltage isn't an issue on this project, because the project will use batteries in series (use batteries in series to increase voltage levels, put them in parallel in order to increase current draw) in order to get my required 7V for the 5V voltage Regulator,  So even if for some reason the 2 batteries are not discharging equally one cell will stay at 3.7 and the other will drop to 3.3 before the voltage regulator starts to be unstable. 

    The advantage of using a LiPo is that they can output a lot of current.  The batteries that I am using have a C rating of 2C.  If we multiply the C value by the mAh value we will get the maximum allowable current draw.   


Max_Safe_Current_Output = C_Rating * mAh_Rating


    So my battery can output 2C * 860mAh = 1720mA or 1.72 Amp.  Way more then we need for our Micro MAX7456 and Camera Receiver (860mAh according to the datasheet).

    If we want to calculate how long the battery will last (in a perfect world) we could use the following formula:


Time_in_hours = Current_Draw_of_All_Devices / battery_mAh


    So, my project should be able to last for about 500mA (receiver) + 85mA (MAX7456) + 35mA (Relay) / 850mAh = 1.37h  add a 20% fudge factor, and we end up with a bit over an hour in operating use, which is perfect for our little toy prototype.  The TV has its own built in battery so its not included in the calculation.


Figure 7: Battery Monitor Circuit


    The circuit is a simple voltage divider, the only thing that we have to make sure is that the maximum voltage that the voltage divider can output is lower then our AVR Vcc value.  In my case the max the battery will output is 4.2 + 4.2 = 8.4V,  the min value that I need for my Voltage Regulator is 7V.  Therefore, if I divide the value by I will get a value of 4.2 to 3.5,  This gives us about 0.7V worth of detection for our ADC,  The biggest problem with this method is that the LiPo cell does not have a constant voltage drop as it discharges, so the system drops from full to 3/4s fairly quicly, spends a lot of time on 3/4 and 1/2 and then plummets like a rock down to low battery.  If we combine the ADC, the timer and a bit of fancy math we could build a more accurate meter.





SOFTWARE:


    This project uses 2 different programs.  One is used in order to upload costume characters to the MAX7456 (again thanks to kiwi john for the code).  The other is the working program which is described below.



MAX7456:

    I used the sample code from Sparkfun in order to run the MAX7456 and followed John's example in order to add custom images.  The big difference between me and john is that I wrote a program called QMIG in VB to generate my code.  Since I'm using John's code at this stage, visit his site and look around.  Props where props are due, right.



Joystick:

Figure 8: Joystick Math


    Because the joystick is a circle (and not a square) we can't just use the value of the ADC for our controls ... well we can but they won't be as smooth as what we are use to from video game controllers.  In order to make things work the way that we are use to, we need to use the distance from the center (ie. the length of the line between the mid-point and the joystick location) for the movement speed and the offset of X from the center as the rate of turn (how sharp of a turn we need).  This can be done by using the Pythagorean theorem.  The formula is modified for each quadrant of the circle (see figure 8) so, our software has to detect what quadrant we are in before and then apply the proper formula.


ATmega8 & ATmega328 Code:

volatile uint8_t joystickx;    // ADC interrupt stores its values here
volatile uint8_t joysticky;    // ADC interrupt stores its values here


/*  joystickx_old & joysticky_old are declared within my main() function */


uint8_t Joystick_Movement(uint8_t *joystickx_old, uint8_t *joysticky_old)
{
    uint8_t joyflag = 0;
    uint8_t joyx_tmp = 0;
    uint8_t joyy_tmp = 0;
    uint8_t joystickc = 0;

    if( (*joysticky_old > joysticky - 4) && (*joysticky_old < joysticky + 4) )
joyflag++;
    if( (*joystickx_old > joystickx - 4) && (*joystickx_old < joystickx + 4) )
joyflag++;


    if( joyflag > 0 )
    {
        *joystickx_old = joystickx;
        *joysticky_old = joysticky;

        // quadrant detection
        if (joystickx > 0x80)
         joyx_tmp = joystickx - 0x80;
        else if (joystickx < 0x80)
joyx_tmp = 0x80 - joystickx;

        if (joyy_tmp > 0x80)
joyy_tmp = joyy_tmp - 0x80;
        else if (joyy_tmp < 0x80)
            joyy_tmp = 0x80 - joyy_tmp;


        // pie calculation
        joystickc = FastIntSqrRoot( (joyx_tmp*joyx_tmp)+(joyy_tmp*joyy_tmp) );


        /* my code generates the required USART signals here but,
           I decided to give you an return value instead because,
           it makes it a more universal example */

        return joystickc;
}
}



    The biggest problem with these formulas is that the sqrt() function in C uses alot of clock cycles. In order to save some clock cycles I recommend using the Fast Integer Square Root method.  It works quite well.

    Now we don't want ot call these functions all the time, so to save on the math, we can save an old value of the ADC, if the value is within the 4 on both pots we can skip the calculations.


ATmega8 & ATmega328 Code:
// Fast Integer Square Root
// Sqrt(128^2 + 128^2) = 181.02 so we only need an 8bit register
uint8_t FastIntSqrRoot (int root)
{
    uint8_t tmp = 0;
    for (int8_t i=7; i > -0; i--)
    {
        tmp |= (1 << i);
        if ( (tmp * tmp) > root)
        tmp ^= (1 << i);
    }
    return tmp;
}



Keypad:

    I explained most of the theory of operation within the Hardware section.  So lets just show you a bit of code.  The code itself is designed to only register single key presses.  Sorry, guys, I don't need multiple keypad key capability on this project so I'm not going to program for it.  Note however, that I used a custom delay function, so you'll need to provide your own delay for stable keypad operation. 


ATmega8 & ATmega168/328 Code:
// handles the keypad stuff
char keypad(void)
{
    // row 1: PD4, row 2: PD5, row 3: PD6, row 4: PD7
    // col 1: PC3, col 2: PC4, col 3: PC5

    char temp;
    char returnvalue = 0xFF;

    // col 1
    PORTC &= ~(1<<3); // clear col 1
    PORTC |= ( (1<<4)|(1<<5) ); // set col 2 & 3

    delay_us(50);

    temp = (~PIND) & 0b11110000;

    if (temp < 0b11110000)
    {
        if (temp & (1<<4))
            returnvalue = 1;
        else if (temp & (1<<5))
            returnvalue = 4;
        else if (temp & (1<<6))
            returnvalue = 7;
        else if (temp & (1<<7))
            returnvalue = 0x0E;
    }
    // end col 1

    // col 2
    PORTC |= (1<<3); // set col 1
    PORTC &= ~(1<<4); // clear col 2
    PORTC |= (1<<5); // set col 3

    delay_us(50);

    temp = (~PIND) & 0b11110000;

    if (temp < 0b11110000)
    {
        if (temp & (1<<4))
            returnvalue = 2;
        else if (temp & (1<<5))
            returnvalue = 5;
        else if (temp & (1<<6))
            returnvalue = 8;
        else if (temp & (1<<7))
            returnvalue = 0;
    }
    // end col 2

    // col 3
    PORTC |= (1<<3); // set col 1
    PORTC |= (1<<4); // set col 2
    PORTC &= ~(1<<5); // clear col 3

    delay_us(50);

    temp = (~PIND) & 0b11110000;

    if (temp < 0b11110000)
    {
        if (temp & (1<<4))
            returnvalue = 3;
        else if (temp & (1<<5))
            returnvalue = 6;
        else if (temp & (1<<6))
            returnvalue = 9;
        else if (temp & (1<<7))
            returnvalue = 0x0F;
    }
    // end col 3

    PORTC |= ( (1<<3)|(1<<4)|(1<<5) ); // set col 1, 2 & 3

    delay_us(50);

    return returnvalue;
}

    
    The de-bounce code was implemented within my main routine:


ATmega8 & ATmega168/328 Code:
void main (void)
{
    uint8_t keypad_old = 0xFF;
    uint8_t keypressed = 0xFF;

    // Main loop
    while(1)
    {
        keypressed = keypad();

        if (keypressed != 0xFF && keypad_old == 0xFF)
        {
            // Do something when a button is pressed
            keypad_old = keypressed;
        }
        
        keypad_old = keypressed;
    }  // end main loop
} // end main function





    Ok thats it for the teaser, I will put more info up as I continue to build the project.






REFERENCE DOCUMENTATION:


Comments