I/O question in programming

How do I intialize multipule I/O port in one program. I have the number 5 captering the north signal of the IR beacon, but wish to capture all four sides.

Thanks,

Hello.

I’m not completely clear on what you’re asking. To set an I/O pin as an input, you clear that pin’s bit in the proper DDR register. For example, if I want to designate pin PC5 as an input, I would use the following line:

DDRC &= ~(1 << PC5);

If I wanted to designate all of the pins of port C as inputs, I would use the following line:

DDRC = 0;

To set an I/O pin as an output, I would set its associated DDR bit. Does this answer your question?

- Ben

Ben,

Perhaps there is more than one way to do this. Indeed need inputs. look at this code:

ADCSRA = 0x87;      // bit 7 set: ADC enabled 
                  // bit 6 clear: don't start conversion 
                  // bit 5 clear: disable autotrigger 
                  // bit 4: ADC interrupt flag 
                  // bit 3 clear: disable ADC interrupt 
                  // bits 0-2 set: ADC clock prescaler is 128 

   

	ADMUX = 0x05;      // bit 7 and 6 clear: voltage ref is Vref pin 
                  // bit 5 set: right-adjust result (10-bit ADC) 
                  // bit 4 not implemented 
                  // bits 0-3: ADC channel (channel 5) 

ADCSRA |= ( 1 << ADSC );         // start conversion 
    while ( ADCSRA & ( 1 << ADSC ))      // wait while converting 
        ; 
    sum += ADC;                     // add in conversion result 

I can see a change in input if I make ADMUX = 0x05; to ADMUX = 0x04; However I can’t make it do both 4 and 5 or more at the same time by using this code. Is yours a better way if so what would I change in mine to use yours.

Thanks,

Hello,

It looks like you’re looking at the analog-to-digital converter; the IR beacon just has digital outputs, so you don’t need the ADC. (The ADC module just has a single ADC with a multiplexer in front of it, so it’s not possible to do more than one channel at a time.)

- Jan

OK, so if I don’t use the ADC what should I use on the oragatan 168 to receive input from all four sides of the IR beacon?

You can use any of the general I/O (input/output) pins on the ATMega168 as digital inputs from your IR beacon, so you just need to pick four that are available and convenient.

On the Orangutan, there are twelve pins brought out to the header, ten of which are general I/O (the other two are dedicated ADC pins, which can only be used as analog inputs). So, your choices are (from left to right):

PC5, PC4, ADC6 and ADC7 (are not useful right now), PC0, PC1, PC2, PC3, PD0, PD1, PB6, PB7.

You should choose four pins that aren’t associated with other hardware you might want to use later. For example, PC4 and PC5 are also the I2C bus pins (or TWI as Atmel likes to call it). PB6 and PB7 can be used as the external oscillator pins if you want to run your microcontroller on a faster clock. PD0 and PD1 are the Serial USART transmit and receive pins.

I would choose PC0, PC1, PC2, PC3, since those pins aren’t associated with much other dedicated hardware, but they can be set up as pin-change interrupts, which might be useful to you later in your IR Beacon program. Also, they’re conveniently in an ordered block.

A super-simple program to read these pins as digital inputs would look something like this:

#include <avr/io.h>

int main(){
	DDRC&=~((1<<PC0)|(1<<PC1)|(1<<PC2)|(1<<PC3));//set PC0-PC3 as inputs
	//not entirely necessary, as they're already inputs by default

	while(1){
		if(!(PINC&(1<<PC0))){
			//north detected, do something
		}
		if(!(PINC&(1<<PC0))){
			//east detected, do something
		}
		if(!(PINC&(1<<PC0))){
			//south detected, do something
		}
		if(!(PINC&(1<<PC0))){
			//west detected, do something
		}
	}

	return 0;
}

The digital outputs of the IR Beacon are normally high, and go low when they detect IR light in a particular direction. The comments make some assumptions about how you chose to wire the connections. Does this make more sense now?

-Adam

Adam,

Not sure I completely understand, but will play later tonight when I get home from work.

How does one learn all this stuff?

Thanks again,

Pretty much the way you’re learning now, by working it out and asking questions. Unfortunately this way sometimes involves breaking things.

If you’re interested, there’s a book/kit called “C Programming for Microcontrollers” I found very helpful when I started learning to program AVR microcontrolers here.

-Adam

Adam,

What do you mean about PC4 and PC5 being programming pins? The ones I know of are on PORTB (and PC6, for reset).

- Jan

My mistake. PC4 and PC5 are the I2C pins, not the SPI pins. I still wouldn’t want to use them up if I didn’t really need to.

-Adam

P.S. I corrected that post

OK guys we are looking good. I have the beacon posting the 4 sides to the LCD like I need. Except it is in integers 0,1,2,3. How do I convert an integer to text?

If your integers are between zero and nine (inclusive) you can convert them to the ASCII text character code by adding 48, or by adding the text for zero. If X is your integer value, and Y is your ASCII character, just use Y=X+48, or Y=X+‘0’.

-Adam

Adam, you lost me here which I admit doesn’t take much. Here is the code you helped me on earlier with an added twist.

int main() 
{ 
   lcd_init();   // this function must be called before any other LCD command 

DDRC&=~((1<<PC0)|(1<<PC1)|(1<<PC2)|(1<<PC3));//set PC0-PC3 as inputs 
   //not entirely necessary, as they're already inputs by default 

	


   while(1){ 
      if(!(PINC&(1<<PC0))){ //north detected, do something
	   DDRC = 0;
      } 
      if(!(PINC&(1<<PC1))){ //east detected, do something 
	  DDRC = 1;
      } 
      if(!(PINC&(1<<PC2))){ //south detected, do something
	  DDRC = 2;
      } 
      if(!(PINC&(1<<PC3))){ //west detected, do something
	  DDRC = 3;
	  }

	


      lcd_gotoxy(0, 0);            // go to the start of LCD line 1 
      lcd_string("Beacon"); 
      lcd_gotoxy(4, 1);            // go to the start of LCD line 2 
      lcd_int(DDRC);               // display average ADC as an integer 
      lcd_string("      ");            // overwrite any LCD character remnants 
   } 


   return 0; 
}

Whoops! DDRC (Data Direction Register for port C) controls the input/output state of the pins of PortC, you don’t want to use it as a variable!

I’m glad you’ve got something working, but I’m a little confused. What would you like the LCD to display?

-Adam

I need the LCD to display wether north, south, east, or west is being detected. I wasn’t sure how to call the correct variable, but DDRC is working as an integer. Looks like you may know the correct way to do this?

Hey once Adam told me not to use DDRC as a variable I figured it out. I set int post and let DDRC = post.

 int post;
	 DDRC = post;


   while(1){ 
      if(!(PINC&(1<<PC0))){ //north detected, do something
	   post = "Center";
      } 
      if(!(PINC&(1<<PC1))){ //east detected, do something 
	  post = "Right";
      } 
      if(!(PINC&(1<<PC2))){ //south detected, do something
	  post = "Rear";
      } 
      if(!(PINC&(1<<PC3))){ //west detected, do something
	  post = "Left";
	  }

	


      lcd_gotoxy(0, 0);            // go to the start of LCD line 1 
      lcd_string("Beacon"); 
      lcd_gotoxy(0, 1);            // go to the start of LCD line 2 
      lcd_string(post);               // display average ADC as an integer 
      lcd_string("      ");            // overwrite any LCD character remnants 
   } 

Thanks a bunch!

Generally you use the microcontroller registers to configure things, like pin states or hardware settings. If you want to store something internally, like an integer, you can create a variable like this:

unsigned int x=350;
signed char y=5;

In the AVR 8-bit microcontrollers, char is a single byte character, or integer number from 0 to 255 (or -128 to +127 if its signed), and int is a two-byte integer number from 0 to 65536 (or -32768 to 32767 if its signed).

But, if just want to display the state on the screen, you can use the screen hardware to store the state like this:

int main(){
	lcd_init(); // this function must be called before any other LCD command

	DDRC&=~((1<<PC0)|(1<<PC1)|(1<<PC2)|(1<<PC3));

	lcd_gotoxy(0, 0); // go to the start of LCD line 1
	lcd_string("Beacon");

	while(1){
		lcd_gotoxy(4, 1); // go to the start of LCD line 2

		if(!(PINC&(1<<PC0))){ //north detected, do something
			lcd_string("N"); // write a character
		}else{
			lcd_string(" "); // write a space
		}

		if(!(PINC&(1<<PC1))){ //east detected, do something
			lcd_string("E"); // write a character
		}else{
			lcd_string(" "); // write a space
		}

		if(!(PINC&(1<<PC2))){ //south detected, do something
			lcd_string("S"); // write a character
		}else{
			lcd_string(" "); // write a space
		}

		if(!(PINC&(1<<PC3))){ //west detected, do something
			lcd_string("W"); // write a character
		}else{
			lcd_string(" "); // write a space
		}
	}

	return 0;
}

I don’t have my Orangutan at home to check, but I think this will work (does it?). You might also want to add a delay to the poll loop, since it’s probably updating the screen faster than you can actually see.

-Adam

Just saw your last post, glad it’s working for you.

By the way, DDRC controls weather the pins of PORTC are inputs or outputs, so there’s no reason to set DDRC=post. The pins are inputs by default, I just included that line to make sure in case you ended up adding more code later.

-Adam

Adam, this code works, and is much cleaner. Now I am interested in the delay you mentioned.

I’m assuming you’re using the most recent Otangutan-lib libraries, and AVRStudio to compile and program your code. If this is not the case, let me know.

First, to make an accurate delay, you need to know the frequency at which your AVR is running. If you’re code has the line:

#include "device.h"

up at the top (which it should) then your code is assuming that the AVR is running at full speed on it’s internal clock, which is 8MHz. New AVR’s come set to run at 1MHz instead, so you should check the fuses that determine clock speed.

In AVRStudio, make like you’re going to download a new program to your Orangutan (power it on, plug it in, go to the programmer menu). The programmer window should have a set of tabs at the top that say [Program][Fuses][LockBits]… Click on the [Fuses] tab.

Side Note: It is very important to be careful when setting the fuses. In particular you do not want to check any of the fuses that start with the word “Ext.” (and there are a lot of them). This will make your AVR try to operate on an external clock signal, and since you don’t have an external clock hooked up, your Orangutan will be frozen until you hook one up, or send it back to Pololu. It’s like chopping vegetables. It’s easy enough, just don’t cut yourself!

Anyway, you should be looking for the fuse that says “Divide clock by 8 internally”. If it is checked, uncheck it, and click on the program button below the list of fuses to make the change. If it is not checked, that’s great, leave it alone.

Up at the top of your code, where the #include statements are, you will need to add the line:

#include <util/delay.h>

Then, wherever you want a delay, add in the line:

_delay_ms(10);

As I’m sure you’ve guessed, this will pause your code for 10 milliseconds. You can change the number 10, but because of the structure of the _delay_ms function, you can only use it for up to 32 milliseconds at a time (if you use a number larger than 32 in the function, it will still only delay for about 32 milliseconds). To get longer delays, you can call the delay function multiple times.

Right now the while loop in your code only takes about 140 microseconds (0.14 milliseconds) to execute, so you’re checking the state of the IR beacon over seven thousand times per second! That’s not necessarily bad, but it’s not necessary. Lets say you wanted to check the state of the IR beacon twenty times per second. You would want a 50 millisecond delay each loop (neglecting the o.14 milliseconds the rest of the code takes to execute). So, the resulting code would look like this:

#include <avr/io.h>
#include "device.h"
#include "lcd.h"
#include <util/delay.h>

int main(){
	lcd_init(); // this function must be called before any other LCD command

	DDRC&=~((1<<PC0)|(1<<PC1)|(1<<PC2)|(1<<PC3));

	lcd_gotoxy(0, 0); // go to the start of LCD line 1
	lcd_string("Beacon");

	while(1){
		lcd_gotoxy(4, 1); // go to the start of LCD line 2

		if(!(PINC&(1<<PC0))){ //north detected, do something
			lcd_string("N"); // write a character
		}else{
			lcd_string(" "); // write a space
		}

		if(!(PINC&(1<<PC1))){ //east detected, do something
			lcd_string("E"); // write a character
		}else{
			lcd_string(" "); // write a space
		}

		if(!(PINC&(1<<PC2))){ //south detected, do something
			lcd_string("S"); // write a character
		}else{
			lcd_string(" "); // write a space
		}

		if(!(PINC&(1<<PC3))){ //west detected, do something
			lcd_string("W"); // write a character
		}else{
			lcd_string(" "); // write a space
		}

		_delay_ms(30);
		_delay_ms(20);
	}

	return 0;
}

If you wanted to use the delay function in a program you were writing without using the Orangutan-lib libraries, you would just include these two lines at the top of your code:

#define F_CPU 8000000
#include <util/delay.h>

Where F_CPU is the frequency of your AVR clock in Hz. If you look through the code of device.h, you’ll find a line of code just like this one.

So, delay away!

-Adam