NerdKits - electronics education for a digital generation

You are not logged in. [log in]

NEW: Learning electronics? Ask your questions on the new Electronics Questions & Answers site hosted by CircuitLab.

Microcontroller Programming » Variation on 3 bit adder

January 22, 2012
by Blackwatch42nd
Blackwatch42nd's Avatar

Hi folks, My first time posting and really (I mean REALLY) new at this micro controller stuff. After I had gone through the exercises in the maunal, I decided to try a slight variation on the 3 Bit Adder (Bitwise Arithmetic). I also wanted to try to refer to the book as little as possible (well that didn't last long). For reasons I'm still haven't figured out, I wired the DIP switch a little strange: BP1 -> Sw1 BP2 -> Sw2 BP3 -> Sw3 CP1 -> Sw6 CP2 -> Sw7 CP3 -> Sw8 But, because the way the DIP switch was oriented I wanted the LSBs to be Sw3 and Sw8. What ended up happening was the bits were reversed in the register from the way I wanted. Now I realize I could have switched the wires around but that would have been too easy, soooo ...

I wrote a function to reverse the bits. I could not find anything, anywhere to do this, so I would like ya'll (I'm from Texas) to tell me if there is a better way to do it (program wise) and whats a good way to out a binary number to the display other than how I did it, with text?

I'm including the whole code (is that too much?) and thanx in advance

John

//
// 3bit_add.c
// add two, 3 bit numbers inputed by toggle switches and display the answer
//    on the multi line display
//

#define F_CPU 14745600

#include <stdio.h>
#include <math.h>
#include <inttypes.h>

#include <avr/pgmspace.h>
#include <avr/interrupt.h>
#include <avr/io.h>

#include "../libnerdkits/delay.h"
#include "../libnerdkits/lcd.h"
#include "../libnerdkits/uart.h"

//
// function to initialize pins B1, B2, B3, C1, C2 and C3 to input
//
void init_pins(void)
{
    DDRB &= ~(1<<PB1); // Sets port pins to input
    DDRB &= ~(1<<PB2);
    DDRB &= ~(1<<PB3);
    DDRC &= ~(1<<PC1);
    DDRC &= ~(1<<PC2);
    DDRC &= ~(1<<PC3);

    PORTB |= (1<<PB1); // Sets port pull resistors "on" to Vcc (5vdc)
    PORTB |= (1<<PB2);
    PORTB |= (1<<PB3);
    PORTC |= (1<<PC1);
    PORTC |= (1<<PC2);
    PORTC |= (1<<PC3);
}

//
// Function to take upto 16 unsigned bits and reverse their order.
//  NumBits is the number of bits wished to reverse begining with the LSB
//  Numb are the bits to be reveresed
//
uint16_t ReverseBits(int NumBits, uint16_t Numb)
{
    uint16_t inter;
    uint16_t answ = 0;
    int i;
    for(i=1; i<= NumBits; i++)
    {
        answ = answ << 1;
        inter = Numb & 1;
        answ = answ | inter;
        Numb = Numb >> 1;
    }
    return answ;
}

int main()
{
    // Initialize ports and pins
    init_pins();

    // Initialize lcd display
    lcd_init();
    lcd_clear_and_home();
    FILE lcd_stream = FDEV_SETUP_STREAM(lcd_putchar, 0, _FDEV_SETUP_WRITE);

    // varibles for 3 bit numbers and answer
    uint8_t firstnumb = 0;
    uint8_t firsttempnumb = 0;
    uint8_t secondnumb = 0;
    uint8_t secondtempnumb = 0;
    uint8_t answer = 0;

    // set up diplay
    lcd_home();
    lcd_goto_position(0,1);
    lcd_write_string(PSTR("Port B"));
    lcd_goto_position(0,10);
    lcd_write_string(PSTR("Port C"));

    while(1)
    {
        //
        // Read port B pins and out put binary to display (as text).
        //          PB3(lsb) -> PB1(msb)    
        //

        lcd_goto_position(1,4);

        if (PINB & (1<<PB3))
        {
            lcd_write_string(PSTR("1"));
        } 
        else
        {
            lcd_write_string(PSTR("0"));
        }

        lcd_goto_position(1,3);
        if (PINB & (1<<PB2))
        {
            lcd_write_string(PSTR("1"));
        } 
        else
        {
            lcd_write_string(PSTR("0"));
        }

        lcd_goto_position(1,2);
        if (PINB & (1<<PB1))
        {
            lcd_write_string(PSTR("1"));
        } 
        else
        {
            lcd_write_string(PSTR("0"));
        }

        //
        // Read port C pins and out put binary to display (as text). 
        //          PC3(lsb) -> PC1(msb)    
        //

        lcd_goto_position(1,13);
        if (PINC & (1<<PC3))
        {
            lcd_write_string(PSTR("1"));
        } 
        else
        {
            lcd_write_string(PSTR("0"));
        }

        lcd_goto_position(1,12);
        if (PINC & (1<<PC2))
        {
            lcd_write_string(PSTR("1"));
        } 
        else
        {
            lcd_write_string(PSTR("0"));
        }

        lcd_goto_position(1,11);
        if (PINC & (1<<PC1))
        {
            lcd_write_string(PSTR("1"));
        } 
        else
        {
            lcd_write_string(PSTR("0"));
        }

        firstnumb = ((PINB & 14) >> 1);
        secondnumb = ((PINC & 14) >> 1);

        //
        // ReverseBits() function calls
        //

        firsttempnumb = ReverseBits(3, firstnumb);
        secondtempnumb = ReverseBits(3, secondnumb);
        answer = firsttempnumb + secondtempnumb;

        lcd_goto_position(3,3);
        lcd_write_int16(firsttempnumb);
        lcd_goto_position(3,8);
        lcd_write_string(PSTR("+"));
        lcd_goto_position(3,12);
        lcd_write_int16(secondtempnumb);
        lcd_goto_position(3,14);
        fprintf_P(&lcd_stream, PSTR("=  %2oo"), answer);
    }
}
January 22, 2012
by JimFrederickson
JimFrederickson's Avatar

I don't see anything that needs to be changed in your "ReverseBits" function.

Assuming am assuming that you want to output the values of the pins one at a time as they are read.

So would, at least, make this change...

    //
    // Read port B pins and out put binary to display (as text).
    //          PB3(lsb) -> PB1(msb)   
    //

    lcd_goto_position(1,2);

    if (PINB & (1<<PB1))
    {
        lcd_write_data('1');
    }
    else
    {
        lcd_write_data('0');
    }

This gets rid of a few function calls per bit-group. (And the cursor only gets positions once.)

The LCD goes to the next position on a line automatically, so you can take advantage of that.

Ultimately though I would display the bits directly from the firstnumb and secondnumb uint8_t values.

    lcd_goto_position(1,2);

lcd_write_data('0', (firstnumb & 1));
lcd_write_data('0', ((firstnumb >> 1) & 1));
lcd_write_data('0', ((firstnumb >> 2) & 1));

or

    lcd_goto_position(1,2);

lcd_write_data('0', ((firstnumb >> 2) & 1));
lcd_write_data('0', ((firstnumb >> 1) & 1));
lcd_write_data('0', (firstnumb & 1));

This approach gets rid of more of the function calls, and I think it is easier to read as well.

(Hopefully this is correct and doesn't lead you astray... I didn't 'test it'! But if it is not entirely correct it is quite close.)

January 23, 2012
by Blackwatch42nd
Blackwatch42nd's Avatar

Jim, Thanx for the quick reply.

I think I understand the need for fewer function calls (less overhead) but I guess the question becomes a point of diminishing returns. How do you tell when it's better to write the same code 3 different times or call a function 3 times? Hmmm?

With regard to the "lcd_write_data" call, I'm not sure how to use it (or some of the other lcd function, ie byte, nibble). "lcd_write_data" seems like it only takes one argument and your example passes two. I do like you first alternate solulution. It is cleaner and easier to read, but you'd have to start with the 3rd bit (msb) to make it read correctly on the the display.

Is there some place that explaines all these function calls without having to decipher the include files?

Thanx again John

January 23, 2012
by Blackwatch42nd
Blackwatch42nd's Avatar

Jim,

Oops, sorry. You were correct about the order. I miss read my own bit order.

John

January 23, 2012
by JimFrederickson
JimFrederickson's Avatar

Hello John,

Sorry about the function call...

The ',' should be '+' on all of the calls.

So:

    lcd_write_data('0' + (firstnumb & 1));
    lcd_write_data('0' + ((firstnumb >> 1) & 1));
    lcd_write_data('0' + ((firstnumb >> 2) & 1));

What happens is that you start with an ASCII '0' character value and then you add the appropriate bit-value to it.

They way it will display and ASCII '0' if the bit is clear, or an ASCII '1' of the bit is set.

If this manner the 'if statements' do not have to be used to get the same affect.

(Sorry for the confusion, caused from my mistake...)

January 23, 2012
by JimFrederickson
JimFrederickson's Avatar

As for the bit patterns I displayed one value both ways to show lsb first and msb first. (Well most least significant bit and most significant bit from 'firstnumb'.)

gets the lsb of firstnumb, using a mask to only keep bit 0.

    (firstnumb & 1)

Shifts 1 bit right, (divides by 2), firstnumb and so bit position 1 is in bit position 0, then uses a mask to keep bit 0.

    ((firstnumb >> 1) & 1)

Shifts, (divides by 4), firstnumb 2 positions so that position 2 is in bit position 0, then uses a mask to keep bit 0.

    ((firstnumb >> 2) & 1)
January 23, 2012
by JimFrederickson
JimFrederickson's Avatar

The 'libnerkits files' are files that they created explicitly to for their product.
As far as I know there is no actual documentation on what is available in those files.

The good part though, is that the files are mostly small and learning by working example is often easier than just ready about something. (Being able to read someone else's code and seeing how someone else codes things is always a good thing.)

I am not sure if there is a clear way "you tell when it's better to write the same code 3 different times or call a function 3 times"..

Writing the same code 3 times can use up a lot more space.

Also if you have to change it then you have multiple places to change it, unless you have called a function then there is only 1 place.

Also if you write the code out 3 times often runs faster.

(There are also 'macros' that can work like 'inline functions' so that there is still only 1 place to change how it works.)

For me I am always consciously aware of several factors whenever I code something.

My two most important factors are:

1 - Do I think that I will be able to read and understand what I did today, in 2-3 months from now...

2 - Can it be changed easily, or made easier to change, in the future.

This is my single most important criteria, and affects about 80%-90% of what I do and how I do it.

Other factors are:

3 - Do I need to adhere to some specific standard or specification?
4 - Do I require speed? 5 - Do I need to conserve Data Space? 6 - Do I need to conserve Program Space?

This is the 'rough order' and the order can be changed depending on 'degree'. (For instance I VERY MUCH avoid the c floating point libraries because they tend to be quite large, and in my tests it seems to be confusing to the optimization routines so anytime I use them I have already very carefully considered their need as well as potential alternatives.)

Back to this little 'microcosm' of 'displaying binary'...

(Okay the following code has ALL BEEN COMPILED so the syntax is correct and functioning! this time... :( )

Example 1:

    void myuart_writebinary(uint8_t ibinary) {
    //  write high nibble
            myuart_write('0' + ((ibinary >> 7) & 1));
            myuart_write('0' + ((ibinary >> 6) & 1));
            myuart_write('0' + ((ibinary >> 5) & 1));
            myuart_write('0' + ((ibinary >> 4) & 1));

            myuart_write('-');

    // write low nibble
            myuart_write('0' + ((ibinary >> 3) & 1));
            myuart_write('0' + ((ibinary >> 2) & 1));
            myuart_write('0' + ((ibinary >> 1) & 1));
            myuart_write('0' + ((ibinary >> 0) & 1));
        }

Example 2:

    void myuart_writebinary2(uint8_t ibinary) {
            uint8_t i;

            for (i = 0; i < 8; i++) {
                myuart_write('0' + (ibinary & 1));
                ibinary = (ibinary >> 1);

                if (i == 3) {
                    myuart_write('-');
                    }
                }
        }

Example 3:

    void myuart_writebinary3(uint8_t ibinary) {
            uint8_t i;

            for (i = 0; i < 8; i++) {
                if ((ibinary & 128) == 128) {
                    myuart_write('1');
                    }
                else {
                    myuart_write('0');
                    }

                ibinary = (ibinary << 1);

                if (i == 3) {
                    myuart_write('-');
                    }
                }
        }

Example 4:

    void myuart_writebinary4(uint8_t ibinary) {
            uint8_t i;

            for (i = 0; i < 4; i++) {
                if ((ibinary & 128) == 128) {
                    myuart_write('1');
                    }
                else {
                    myuart_write('0');
                    }

                ibinary = (ibinary << 1);
                }

            myuart_write('-');

            for (i = 0; i < 4; i++) {
                if ((ibinary & 128) == 128) {
                    myuart_write('1');
                    }
                else {
                    myuart_write('0');
                    }

                ibinary = (ibinary << 1);
                }
        }

Example 5:

    void myuart_writebinary5(uint8_t ibinary) {
            uint8_t i;
            uint8_t ichar;

            ichar = '0';

            for (i = 0; i < 8; i++) {
                if ((ibinary & 128) == 128) {
                    ichar++;
                    myuart_write(ichar);
                    ichar--;
                    }
                else {
                    myuart_write(ichar);
                    }

                ibinary = (ibinary << 1);

                if (i == 3) {
                    myuart_write('-');
                    }
                }
        }

The functions produce output as:

    1110-1101

I use the first function.

I use it because to me it is easy to read and looks better. I can just 'glance at it' and very quickly see my intentions.

Now, that beings said...

Here are the actual sizes of each function: (As compiled by my configuration of the AVR-GCC Environment.)

myuart_writebinary - 120 bytes myuart_writebinary2 - 42 bytes (Note display LSB ->> MSB, backwards) myuart_writebinary3 - 48 bytes myuart_writebinary4 - 68 bytes myuart_writebinary5 - 48 bytes

So the 'one I prefer' is the largest one. (NOTE: Putting in the '-' between the nibbles of the byte costs about 10bytes of program space, but for me the improved readability of the output is important.)

Am I concerned about the the size difference?

For me not really.

If I was running short of 'code space' then I likely would change things, but for what I do it has never been a real factor.

I am sure there are many other ways, better/smaller, to output a binary number as well. The 'c language' is extremely flexible which is why it has been around so long and has such wide use.

January 23, 2012
by JimFrederickson
JimFrederickson's Avatar

The 'code sections' are there, but they are not showing up on My Browser unless I click the 'View Original' link.

They looked great in the Preview? (Which I dig just before I made the post.

WHY?

I don't know!

Yet...

(maybe I will reboot and see...)

(Can everyone else see the code examples without using the link?)

January 23, 2012
by Blackwatch42nd
Blackwatch42nd's Avatar

Jim, Thanx for the info. It helps alot, though I still need to wade thru and get a good understanding of it. The email I got from the forum was short some of the code and several paragraphs but it looks like it all showed up ok on the website. Seems a little strange. Anyway, thanx again and i have more questions as time goes along. John

Post a Reply

Please log in to post a reply.

Did you know that many systems in nature can be described by a first order response? Learn more...