SparkFun Forums 

Where electronics enthusiasts find answers.

Discussions on the software and hardware for Atmel's STK standard.
By landon
#32158
I have an AVR-IO-M16 board and have written code to toggle the relays, talk out the serial port, and all that works well. My question is about the input side of the board.

I have two questions I hope someone can help answer.

1) In the AVR-IO-M16 schematic from Olimex, it shows pin 5 of the optoisolators attached to a pull up to +5V. Pin 5 should also, I think, be output of the optoisolator which should reflect a level on the input and optoisolator U1's pin5 I think is also connected to pin 11 on the ATmega16 (PD2). Then inputs 2,3, and 4 go to ATMega PA0, PA1, and PA2 respectively.

The problem I'm having is that regardless of actual input I provide on the screw-terminals, I always see +5 on pin 5 of the optoisolator.

2) The 2nd problem I'm having is that on PortA, even though I set the DDRA to 0x00, for all input, when I read portA, I never get any high-states - it's always 0. Given that pin 5 of the optoisolator is always +5, I would have expected to read all 1's off port A pins.

I'm clearly missing something, but this seems so straightforward, I'm baffled by whatever it is that I'm missing. Any clues for the clueless?
By OLIMEX
#32338
1) correct

to read the input make sure you do this:
- supply voltage with enough current capability to light on 2 LEDs i.e. 10mA, also make sure voltage is 5V or above as the voltage drop on the LEDs alone is 2+2 V
- you supply the voltage with correct polarity , if your input LED is ON this means you apply voltage correctly
- measure with voltmeter what go to AVR pins
- make sure your AVR ports are initialized as digital inputs not as ADC
Best regards
Tsvetan
By landon
#32344
Thank you very much for the reply. I've gone through your check list and believe I have addressed each point.

I have a bench supply I'm using for the +5V input (I put in 5.2)...ample current available.

The input light is lit so polarity is correct.

I've initialized the ADCSRA register to 0 which should disable ADC.

I tested the outputs of the optoisolators as a proxy for what's going to the AVR. If there was no input, the optoisolator outputs were high. If there was a high input, the output of the optoisolator was low.

My code below is commented related to the results I would get based on how PORTA was initialized. It seems no matter how I initialize port A and despite I have the DDRA set for input, the inputs never reflect what's coming into them.

Perhaps this is a code issue or perhaps this is a hardware issue, I don't know. Here's the code I'm using to test the functionality - if you see something wrong, please let me know.

Thanks,

Landon
Code: Select all
/*  avriom16.c
 * 
 *   code to drive the Olimex AVR-IO-M16 relay dev board
 * 
 *   this functionality will accept commands from the serial
 *   port and also tie any of the 4 port inputs to their corresponding
 *   outputs so if the input goes high, the relay will fire.
 * 
 *   this way the device can be driven either through GPIO from a micro
 *   or wireless comm device like XBee or LANtronix, or through a direct
 *   connection to a serial device or terminal.
 *
 *   Copyright (C) 2007, 360VL, Inc.  All rights reserved
 *   Copyright (C) 2007, Landon Cox.  All rights reserved
 *
 *   This code is licensed per Apache 2.0 open source licensing.
 */
 

#include <avr/io.h>
#include <stdio.h>
#include <string.h>

#include "bits.h"

// AVR-IO-M16 related defines - derived from the Olimex schematic
#define LEDPORT PORTD
#define	LED		(1<<PD7)

#define RELAYPORT PORTB
#define RELAY1  (1<<PB3)
#define RELAY2  (1<<PB2)
#define RELAY3  (1<<PB1)
#define RELAY4  (1<<PB0)

#define IN1PORT PORTD
#define IN1     (1<<PD2)

#define IN2PORT PORTA
#define IN2     (1<<PA0)

#define IN3PORT PORTA
#define IN3     (1<<PA1)

#define IN4PORT PORTA
#define IN4     (1<<PA2)

// not sure what the fastest baud rate is for AVR mega16 running at 16MHz
// but this works and is fast enough for what we need to do...occasionally flip a relay switch
#define BAUD_RATE 38400ul

// #define FOSC      1000000UL
// #define FOSC 8000000UL
#define UBBR_VAL  ((FOSC/16/BAUD_RATE)-1)

// FOSC is now override defined in makefile
// FOSC of 8000000UL (8 MHz) which is the max of the ATMega8 Olimex AVR-P28 board assumes
// the fuse bits as follows:
//
//  CKOPT  1
//  CKSEL3 1
//  CKSEL2 1
//  CKSEL1 1
//  CKSEL0 1
//  SUT1   1
//  SUT0   0
//
//  In AVR fuse parlance, a bit value of 1 is "unprogrammed" and a bit value of 0 is "programmed".
//  In PonyProg fuse checkbox dialog, an unprogrammed 
//  those fuse bits will cause the crystal oscillator to be used as the clock source
//  so, FOSC must match whatever you use for clock source.
//
//  default ATMega8 settings are:
//  CKOPT  1
//  CKSEL3 0
//  CKSEL2 0
//  CKSEL1 0
//  CKSEL0 1
//  SUT1   1
//  SUT0   0
//
//  These will use the 1MHz internal oscillator as clock source, if you just plug in a raw
//  ATMega8, these will be the settings and FOSC in the Makefile should be 1000000 (1MHz)

void init()
{
	
	// Input/Output Ports initialization
	// Port B
	RELAYPORT = 0x00;
	DDRB=0x0F;  // O1-4 are on PB3-PB0 respectively
	
	// Port C
	PORTC=0x00;
	DDRC=0x20;
	
	// Port D
	PORTD=0x00;
	DDRD=LED;   // IN1 is on port D but is input, so nothing to do on the DDRD for it
	
	// LED is tied to +5V opposite.  avr pin side would be gnd, so take it low
	LEDPORT &= ~LED;
	
	// test - this should fire all 4 relay ports and turn their LEDs on in the process
	// RELAYPORT |= 0x0f;  // send them all high
	
	// test fire 1 relay - this succeeds, no problems
	RELAYPORT |= RELAY1;
	
	// baud rate set
	UBRRH = (uint8_t)(UBBR_VAL >> 8 );
	UBRRL = (uint8_t)(UBBR_VAL);
	
	UCSRB = (1<<RXEN)|(1<<TXEN);
		
	// to access UCSRC which shares the same i/o location as UBRRH, the URSEL bit must
	// be set, otherwise it will update UBRRH instead
	// URSEL - must be 1 to write to UCSRC
	// UMSEL - 0 asynchronous - 1 synchronous
	// USBS - 0 1 stop bit, 1, 2 stop bits
	// UPM1 UPM0 - 10 - even parity, 11, odd parity, 00 disabled (no parity)
	// UCSZ2 UCSZ1 UCSZ0 - 011 (8 bits), 010 (7bits)
	
	UCSRC = (1<<URSEL)|
			(0<<UMSEL)|
			(0<<USBS)|
			(1<<UCSZ1)|(1<<UCSZ0);
	

}

void sendbyte( uint8_t databyte )
{
	// if byte is being transmitted, wait for that
	while((UCSRA&(1<<UDRE)) == 0);
	
	UDR = databyte;
}

uint8_t getbyte()
{
	// wait until a byte is received	
	while( (UCSRA&(1<<RXC)) == 0);
		
	return UDR;
}

void sendStr( char *str )
{
	while ( *str != '\0' )
	{
		sendbyte( (uint8_t)*str++);
	}
}

int main(void) 
{   
	init();
	
	sendStr("test AVR IO driver v1.0\r\n");
	
	while( 1 )
	{
		// read the inputs and match the relays with the input states
		
		unsigned char porta;
		char output[80];
		
		// PORT A
		ADCSRA = 0x00;  // disable ADC 
		
		// PORTA=(IN2 | IN3 | IN4);  // if I set these states, later, PORTA will be read all high, even when input is low
		PORTA=0x00;   // if I set these states on PORTA, later, it will be read all low, even when the input is high
		
		DDRA=0x00;  // all input for now
		porta = PORTA;
		sprintf( output, "porta: %0x\n", porta );
		sendStr( output );   // no matter what is input through the terminals, this is always 0
							 // I also injected 5v into pa7 and pa6 directly but I still only get 0
		
		// these reflect what is read from Port A  - if PORTA is initialized 0x00, regardless of input terminal input, all are low
		// if PORTA is initialized (IN2|IN3|IN4), then regardless of input terminal input, all are high
		
		uint8_t in1, in2, in3, in4;
		in1 = IN1PORT & IN1;
		in2 = IN2PORT & IN2;
		in3 = IN3PORT & IN3;
		in4 = IN4PORT & IN4;
		
		if ( in1 > 0 )
			RELAYPORT |= RELAY1;
		else
			RELAYPORT &= ~RELAY1;
	
		if ( in2 > 0 )
			RELAYPORT |= RELAY2;
		else
			RELAYPORT &= ~RELAY2;
			
		if ( in3 > 0 )
			RELAYPORT |= RELAY3;
		else
			RELAYPORT &= ~RELAY3;
			
		if ( in4 > 0 )
			RELAYPORT |= RELAY4;
		else
			RELAYPORT &= ~RELAY4;		
			

		if ( (in1 | in2 | in3 | in4) > 0 ) 
		{
			sprintf(output,"in1: %08x in2: %08x in3: %08x in4: %08x\n", in1, in2, in3, in4);
			sendStr( output );
		}
		else
		{
			sprintf(output,"in1: %08x in2: %08x in3: %08x in4: %08x\n", in1, in2, in3, in4);
			sendStr( output );
		}
	}
	
	/* disable serial input until I get an interrupt driven serial library running	
	uint8_t databyte;
	

	sendStr("start - type a character and the next character in sequence will be echoed back to you ");
	
	while( 1 )
	{
			databyte = getbyte();
			sendbyte( databyte + 1 );
	}
	*/
	
}


void relay( int num, int value )
{
	// todo convenience function
}




By wiml
#32405
I'm not clear on whether you're seeing the correct voltages on pin 5 of the optos or not. In your first post you say that they're always at +5V, but in your second post it sounds like the opto's output is responding normally to the input on the screw terminals (but still not getting to your AVR program).

Anyway, I think your problem is that you're reading PORTA but should be reading PINA. The AVR has separate locations for the register that holds the output bits (even if the pin is configured as an input) and for the gates that sense the input. This is useful (e.g. it allows you to use read-modify-write instructions on the port pins) but it's confusing if you're coming from an MCU which doesn't work this way.
By landon
#32415
Yes, I'm now seeing an inverted output from the optoisolator (high in to the terminal yields low out from optoisolator.) So, assuming that's correct, they do seem to be working as I would expect. I'm wondering if I had PORTA tri-stated or something like that during those tests causing it to drive +5V towards the optoisolators. Just a guess as to why I saw the first results....I didn't save that particular code/run to go back and compare.

I also noticed as I was restructuring code, the code I posted above had the port setup, ADC disabling, etc within the while loop. That wasn't what I intended.

I have since written a simple serial command processing parser to take a relay number and 0 or 1 for state and it flips relays from the serial console.

Hopefully this weekend I will have a chance to refactor the input side. I will try the PIN read suggestion instead of a full port read, though I'm pretty sure reading the full port would work fine as well (though it's not optimal to read the full port and then &/and off the bit I needed.)

I'll repost code as I get more working. Then at the end of this there will be a working app for the board that people can use for a starting point for WinAVR tools, anyway. Right now Olimex doesn't supply a basic test app, so perhaps this can be that app assuming I can get it to work throughout.

Thanks for the suggestions, wiml. I would really like to get this going as the board could be very useful.
By wiml
#32447
I will try the PIN read suggestion instead of a full port read, though I'm pretty sure reading the full port would work fine as well (though it's not optimal to read the full port and then &/and off the bit I needed.)
No, PORTA and PINA are two different registers, each with one bit per pin. Reading PORTA just returns what you last wrote to it. Reading PINA returns what's on the MCU pins.
By landon
#32469
Thanks again for the clarification.

I finally got a handle on it. I've reposted code that will handle either serial input commands to turn relays on and off or screw-terminal input. So, it demonstrates both simple serial (polling, not interrupt driven) on the ATMega16 as well as how to use the board.

I built this with the WinAVR tools, programmed the device with PonyProg and the serial ICSP programmer from Olimex.

All is good. Thanks for the feedback along the way.
Code: Select all
/*  avriom16.c
 * 
 *   code to drive the Olimex AVR-IO-M16 relay dev board
 * 
 *   this functionality will accept commands from the serial
 *   port and also tie any of the 4 port inputs to their corresponding
 *   outputs so if the input goes high, the relay will fire.
 * 
 *   this way the device can be driven either through GPIO from a micro
 *   or wireless comm device like XBee or LANtronix, or through a direct
 *   connection to a serial device or terminal.
 *
 *   Copyright (C) 2007, 360VL, Inc.  All rights reserved
 *   Copyright (C) 2007, Landon Cox.  All rights reserved
 *
 *   This code is licensed per Apache 2.0 open source licensing.
 */
 

#include <avr/io.h>
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include "delay.h"

#include "bits.h"

// AVR-IO-M16 related defines - derived from the Olimex schematic
#define LEDPORT PORTD
#define	LED		(1<<PD7)

#define RELAYPORT PORTB
#define RELAY1  (1<<PB3)
#define RELAY2  (1<<PB2)
#define RELAY3  (1<<PB1)
#define RELAY4  (1<<PB0)

#define IN1PORT PORTD
#define IN1     (1<<PD2)

#define IN2PORT PORTA
#define IN2     (1<<PA0)

#define IN3PORT PORTA
#define IN3     (1<<PA1)

#define IN4PORT PORTA
#define IN4     (1<<PA2)

// not sure what the fastest baud rate is for AVR mega16 running at 16MHz
// but this works and is fast enough for what we need to do...occasionally flip a relay switch
#define BAUD_RATE 38400ul

// #define FOSC      1000000UL
// #define FOSC 8000000UL
#define UBBR_VAL  ((FOSC/16/BAUD_RATE)-1)

// FOSC is now override defined in makefile
// FOSC of 8000000UL (8 MHz) which is the max of the ATMega8 Olimex AVR-P28 board assumes
// the fuse bits as follows:
//
//  CKOPT  1
//  CKSEL3 1
//  CKSEL2 1
//  CKSEL1 1
//  CKSEL0 1
//  SUT1   1
//  SUT0   0
//
//  In AVR fuse parlance, a bit value of 1 is "unprogrammed" and a bit value of 0 is "programmed".
//  In PonyProg fuse checkbox dialog, an unprogrammed 
//  those fuse bits will cause the crystal oscillator to be used as the clock source
//  so, FOSC must match whatever you use for clock source.
//
//  default ATMega8 settings are:
//  CKOPT  1
//  CKSEL3 0
//  CKSEL2 0
//  CKSEL1 0
//  CKSEL0 1
//  SUT1   1
//  SUT0   0
//
//  These will use the 1MHz internal oscillator as clock source, if you just plug in a raw
//  ATMega8, these will be the settings and FOSC in the Makefile should be 1000000 (1MHz)

void init();
void setupInput();
void mapInput2Relay();
void sendbyte( uint8_t databyte );
uint8_t getbyte();
void sendStr( char *str );
void processCommand( uint8_t relaynum, uint8_t state );

int main(void) 
{   
	init();
	setupInput();
			
	sendStr("360VL AVR IO driver v1.0\r\n");
	
	/* uncomment this while if you want to drive relays by screw terminal input */
	
	while( 1 )
	{
		mapInput2Relay();
	}
	
	
	/* uncomment this while if you want to command the relays by typing serial commands to the board 
	   see processCommand for syntax
	
	uint8_t databyte1,databyte2;
	
	while( 1 )
	{
			databyte1 = getbyte();
			sendbyte( databyte1 );
			databyte2 = getbyte();
			sendbyte( databyte2 );
			
			processCommand( databyte1 - 48,  databyte2 - 48 );
	}
	*/
	
}


void init()
{
	
	// Input/Output Ports initialization
	// Port B
	RELAYPORT = 0x00;
	DDRB=0x0F;  // O1-4 are on PB3-PB0 respectively
	
	// Port C
	PORTC=0x00;
	DDRC=0x20;
	
	// Port D
	PORTD=0x00;
	DDRD=LED;   // IN1 is on port D but is input, so nothing to do on the DDRD for it
	
	// LED is tied to +5V opposite.  avr pin side would be gnd, so take it low
	LEDPORT &= ~LED;
	
	// test - this should fire all 4 relay ports and turn their LEDs on in the process
	// RELAYPORT |= 0x0f;  // send them all high
	
	// test fire 1 relay - this succeeds, no problems
	// RELAYPORT |= RELAY1;
	
	// baud rate set
	UBRRH = (uint8_t)(UBBR_VAL >> 8 );
	UBRRL = (uint8_t)(UBBR_VAL);
	
	UCSRB = (1<<RXEN)|(1<<TXEN);
		
	// to access UCSRC which shares the same i/o location as UBRRH, the URSEL bit must
	// be set, otherwise it will update UBRRH instead
	// URSEL - must be 1 to write to UCSRC
	// UMSEL - 0 asynchronous - 1 synchronous
	// USBS - 0 1 stop bit, 1, 2 stop bits
	// UPM1 UPM0 - 10 - even parity, 11, odd parity, 00 disabled (no parity)
	// UCSZ2 UCSZ1 UCSZ0 - 011 (8 bits), 010 (7bits)
	
	UCSRC = (1<<URSEL)|
			(0<<UMSEL)|
			(0<<USBS)|
			(1<<UCSZ1)|(1<<UCSZ0);
	

}

void sendbyte( uint8_t databyte )
{
	// if byte is being transmitted, wait for that
	while((UCSRA&(1<<UDRE)) == 0);
	
	UDR = databyte;
}

uint8_t getbyte()
{
	// wait until a byte is received	
	while( (UCSRA&(1<<RXC)) == 0);
		
	return UDR;
}

void sendStr( char *str )
{
	while ( *str != '\0' )
	{
		sendbyte( (uint8_t)*str++);
	}
}

/* this is a simple command line processor so instead of controlling relays by screw terminal input,
   you can type commands to it from the serial port to flip the relays.
   The syntax is simply [relay number][0|1].
   Example:
	11
	will turn the relay number 1 on
	10 
	will turn the relay number 1 off
	
	relay numbers go from 1-4
*/

void processCommand( uint8_t relaynum, uint8_t state )
{
		char output[80];
		
		sprintf(output,"relay: %02x, state: %02x\n\r", relaynum, state );
		sendStr( output );
				
		switch( relaynum )
		{
		case 1:if ( state >= 1 )
					RELAYPORT |= RELAY1;
				else
					RELAYPORT &= ~RELAY1;
			break;
			
		case 2:if ( state >= 1 )
					RELAYPORT |= RELAY2;
				else
					RELAYPORT &= ~RELAY2;
			break;
		case 3:if ( state >= 1 )
					RELAYPORT |= RELAY3;
				else
					RELAYPORT &= ~RELAY3;
			break;
		case 4:if ( state >= 1 )
				RELAYPORT |= RELAY4;
					else
				RELAYPORT &= ~RELAY4;
			break;
		default:

			sprintf(output,"unrecognized relay number: %d", relaynum );
			sendStr( output );
			break;
		}

}

void setupInput()
{
	// PORT A
	ADCSRA = 0x00;  // disable ADC 
	
	// PORTA=(IN2 | IN3 | IN4);  // if I set these states, later, PORTA will be read all high, even when input is low
	PORTA=0x00;   // if I set these states on PORTA, later, it will be read all low, even when the input is high
	
	DDRA=0x00;  // all input for now
	
}

void mapInput2Relay()
{
	uint8_t in1=0, in2=0, in3=0, in4=0, porta, portd;

	// a high input on the screw terminal will be a low input to the avr.

	// read IN1 on portD
	portd = PIND;
	
	// mask off just the PD2 bit and turn it into a boolean, re-invert the value so it matches input terminals
	in1 = (~(PIND) >> PD2) & 0x01;
	
	// read the rest of the inputs from port A
	porta = PINA;
		
	// mask off each input and turn it into a boolean - re-invert the value so it matches input terminals
	in2 = (~porta >> PA0 ) & 0x01;
	in3 = (~porta >> PA1 ) & 0x01;
	in4 = (~porta >> PA2 ) & 0x01;

	// flip the relays - if boolean 1, turn the flip it from normal state
	if ( in1 == 1 )
		RELAYPORT |= RELAY1;
	else
		RELAYPORT &= ~RELAY1;

	if ( in2 == 1 )
		RELAYPORT |= RELAY2;
	else
		RELAYPORT &= ~RELAY2;
		
	if ( in3 == 1 )
		RELAYPORT |= RELAY3;
	else
		RELAYPORT &= ~RELAY3;
		
	if ( in4 == 1 )
		RELAYPORT |= RELAY4;
	else
		RELAYPORT &= ~RELAY4;	

}