Using analog joystick in AVR projects

In many cases joystick manipulator is best choice for user input. Weather it is a game, robot or flying machine – joystick is most intuitive way of controlling them. You can actually find them in gaming controllers like PlayStation or XBOX. The one we are going to interface is Thumb Joystick I purchased some time ago from SparkFun. They are really cheap and as users report it is practically same as in XBOX 360 which can be replaces if one is broken.

joystick

I didn’t bother making a PCB for it – just used a breakout board for it which also can be found on SparkFun. Simply speaking this joystick is nothing more than two potentiometers and one pushbutton. It is designed so that potentiometers are oriented perpendicular and thus moving stick you can have X and Y axis control. Push button is simply action button which can be activated by pressing joystick down. So controlling joystick is a matter of analog read of both potentiometers with microcontroller ADC inputs.

The joystick has springs to return thumb stick in to center position. So potentiometers are also centered. If there are 10k potentiometers used, then each value is centered at about 5k. so if we use 10-bit ADC, we get center point at analog value 512.

Connecting joystick to Atmega1280

To since I have couple of Arduino boards, I am gonna use Arduino Mega128 board, but as normal AVR development board which I will program using AVRStudio6.1 and C language. The board has everything needed including Serial to USB interface based on FT232RL, power supply and all pins available for interfacing. In our demonstration we are going to connect joystick to microcontroller as follow:

  • Both potentiometers side pins to VCC and GND;

  • center pin of vertical axis potentiometer to ADC0;

  • center pin of horizontal axis potentiometer to ADC1;

  • push button to PortE pin 4.

thumstick joystick to atmega1280

Now we can proceed to writing a simple program that would read joystick values and send them to serial interface.

thumb_joystick_to_atmega1280

Coding AVR to read joystick

In order to read joystick analog values and send data through serial interface we are going to need ADC and USART routines. For ADC we will need only two functions – one to initialize ADC and second to read ADC. They are implemented as follows:

void InitADC(void)
{
	// Select Vref=AVcc
	ADMUX |= (1<<REFS0);
	//set prescaller to 128 and enable ADC 
	ADCSRA |= (1<<ADPS2)|(1<<ADPS1)|(1<<ADPS0)|(1<<ADEN);
}
uint16_t ReadADC(uint8_t ADCchannel)
{
	//select ADC channel with safety mask
	ADMUX = (ADMUX & 0xF0) | (ADCchannel & 0x0F);
	//single conversion mode
	ADCSRA |= (1<<ADSC);
	// wait until ADC conversion is complete
	while( ADCSRA & (1<<ADSC) );
	return ADC;
}

Simply speaking we set up ADC with 128 prescaller and select reference voltage as VCC. Reading is also simple – we select channel and make single conversion which returns 10 bit ADC value.

As for USART communications, we simply implement stdio.h library based formatted strings. Here also we need several helper functions including

void USART0Init(void);

int USART0SendByte(char u8Data, FILE *stream);

The implementations will be obvious in example code bellow.

In our code we are going to loop through ADC reads, interpret them as speed values and then output them to terminal screen. The speed value will vary depending on how much joystick is pushed. The more it is pushed to the limit the bigger speed value should be in particular direction. First of all here is a demo code:

#include <stdio.h>
#include <avr/io.h>
#include <avr/eeprom.h>
#include <avr/pgmspace.h>
#include <util/delay.h>
#define USART_BAUDRATE 9600
#define UBRR_VALUE (((F_CPU / (USART_BAUDRATE * 16UL))) - 1)
#define BTDDR DDRE
#define BTPORT PORTE
#define BTPIN PINE
#define BT	PE4
#define MAXSPEED 10
//define eeprom addresses
#define EEVMAX 0
#define EEVMIN 2
#define EEVCENTER 4
#define EEHMAX 6
#define EEHMIN 8
#define EEHCENTER 10
#define EEJINIT E2END
//variables
uint16_t vmax, vmin, vcenter, hmax, hmin, hcenter;
//function prototypes
void USART0Init(void);
int USART0SendByte(char u8Data, FILE *stream);
void InitADC(void);
uint16_t ReadADC(uint8_t ADCchannel);
void ButtonInit(void);
uint8_t ButtonRead(void);
void JoystickCalibrate(void);
void JoysticReadParameters(void);	
//set stream pointer
FILE usart0_str = FDEV_SETUP_STREAM(USART0SendByte, NULL, _FDEV_SETUP_WRITE);
int main()
{
	uint16_t vpotval, hpotval;
	int8_t vspeed, hspeed;
	//initialize ADC
	InitADC();
	//Initialize USART0
	USART0Init();
	//assign our stream to standard I/O streams
	stdout=&usart0_str;
	//initialize button
	ButtonInit();
	//read joystick parameters from EEPROM
	//or calibrate joystick for the first time
	JoysticReadParameters();
	while(1)
	{
		vpotval = ReadADC(0);
		hpotval = ReadADC(1);
		//calculate speed values
		if (vpotval > vcenter)
		{
			vspeed = (int8_t)((int16_t)((vpotval-vcenter)*MAXSPEED)/((int16_t)((vmax-vcenter))));
		} else {
			vspeed = (int8_t)((int16_t)((vpotval-vcenter)*MAXSPEED)/((int16_t)((vcenter-vmin))));
		}
		if (hpotval > hcenter)
		{
			hspeed = (int8_t)((int16_t)((hpotval-hcenter)*MAXSPEED)/((int16_t)((hmax-hcenter))));
			} else {
			hspeed = (int8_t)((int16_t)((hpotval-hcenter)*MAXSPEED)/((int16_t)((hcenter-hmin))));
		}
		//print data
		printf("V Potentiometer value = %u \n", (uint16_t)vpotval);
		printf("V speed = %i \n", (int8_t)vspeed);
		printf("H Potentiometer value = %u \n", (uint16_t)hpotval);
		printf("H speed = %i \n", (int8_t)hspeed);
		_delay_ms(1000);
	}
}
//Routines
void USART0Init(void)
{
	// Set baud rate
	UBRR0H = (uint8_t)(UBRR_VALUE>>8);
	UBRR0L = (uint8_t)UBRR_VALUE;
	// Set frame format to 8 data bits, no parity, 1 stop bit
	UCSR0C |= (1<<UCSZ01)|(1<<UCSZ00);
	//enable transmission and reception
	UCSR0B |= (1<<RXEN0)|(1<<TXEN0);
}
int USART0SendByte(char u8Data, FILE *stream)
{
	if(u8Data == '\n')
	{
		USART0SendByte('\r', stream);
	}
	//wait while previous byte is completed
	while(!(UCSR0A&(1<<UDRE0))){};
	// Transmit data
	UDR0 = u8Data;
	return 0;
}
void InitADC(void)
{
	// Select Vref=AVcc
	ADMUX |= (1<<REFS0);
	//set prescaller to 128 and enable ADC 
	ADCSRA |= (1<<ADPS2)|(1<<ADPS1)|(1<<ADPS0)|(1<<ADEN);
}
uint16_t ReadADC(uint8_t ADCchannel)
{
	//select ADC channel with safety mask
	ADMUX = (ADMUX & 0xF0) | (ADCchannel & 0x0F);
	//single conversion mode
	ADCSRA |= (1<<ADSC);
	// wait until ADC conversion is complete
	while( ADCSRA & (1<<ADSC) );
	return ADC;
}
void ButtonInit(void)
{
	//as input
	BTDDR &= ~(1<<BT);
	//enable internal pullup
	BTPORT |= (1<<BT);
}
uint8_t ButtonRead(void)
{
	return (1<<BT)&BTPIN;
}
void JoystickCalibrate(void)
{
	printf_P(PSTR("\nInput Vertical max"));
	while (ButtonRead()) {}
	eeprom_write_word((uint16_t*)EEVMAX,ReadADC(0));//
	printf_P(PSTR("\nVertical MAX stored to EEPROM"));
	while (!ButtonRead()) {}
	printf_P(PSTR("\nInput Vertical min"));
	while (ButtonRead()) {}
	eeprom_write_word((uint16_t*)EEVMIN,ReadADC(0));//
	printf_P(PSTR("\nVertical MIN stored to EEPROM"));
	while (!ButtonRead()) {}
	printf_P(PSTR("\nInput center"));
	while (ButtonRead()) {}
	eeprom_write_word((uint16_t*)EEVCENTER,ReadADC(0));//
	eeprom_write_word((uint16_t*)EEHCENTER,ReadADC(1));//
	printf_P(PSTR("\nV and H center stored to EEPROM"));
	while (!ButtonRead()) {}
	printf_P(PSTR("\nInput Horizontal max"));
	while (ButtonRead()) {}
	eeprom_write_word((uint16_t*)EEHMAX,ReadADC(1));//
	printf_P(PSTR("\nHorizontal MAX stored to EEPROM"));
	while (!ButtonRead()) {}
	printf_P(PSTR("\nInput Horizontal min"));
	while (ButtonRead()) {}
	eeprom_write_word((uint16_t*)EEHMIN,ReadADC(1));//
	printf_P(PSTR("\nHorizontal MIN stored to EEPROM"));
	while (!ButtonRead()) {}
	eeprom_write_byte((uint8_t*)EEJINIT,'T');//marks once that eeprom init is done
	//once this procedure is held, no more initialization is performed
}
void JoysticReadParameters(void)
{
	if (eeprom_read_byte((uint8_t*)EEJINIT)!='T')
	{
		JoystickCalibrate();
		} else {
		printf_P(PSTR("\nReading Joystick parameters...\n"));
		vmax = eeprom_read_word((uint16_t*)EEVMAX);
		vmin = eeprom_read_word((uint16_t*)EEVMIN);
		vcenter = eeprom_read_word((uint16_t*)EEVCENTER);
		hmax = eeprom_read_word((uint16_t*)EEHMAX);
		hmin = eeprom_read_word((uint16_t*)EEHMIN);
		hcenter = eeprom_read_word((uint16_t*)EEHCENTER);
		printf("V max = %u \n", (uint16_t)vmax);
		printf("V min = %u \n", (uint16_t)vmin);
		printf("V center = %u \n", (uint16_t)vcenter);
		printf("H max = %u \n", (uint16_t)hmax);
		printf("H min = %u \n", (uint16_t)hmin);
		printf("H center = %u \n", (uint16_t)hcenter);
		printf_P(PSTR("\nDone!\n"));
	}
}

As you can see I have also implemented a calibration function in to algorithm.

void JoystickCalibrate(void)

 

Normally you would expect potentiometer to at the center that would result in ADC value equal to 512. Also min and max values should be 0 and 1024. But in reality these values may be off at some value. For instance I get vertical center value 498 while horizontal 506. this may lead to some errors in precise control algorithms. During calibration routine I simply measured min and max values including center potentiometer value and stored to EEPROM memory. It has to be done once on first program run. Later program checks if calibration were performed and reads those values to RAM to be used in calculations.

void JoysticReadParameters(void)

 

The code is only to demonstrate the use of Joystick in your projects. So calculation of speed and terminal messages are only to illustrate how things work.

Joystick output on terminal screen

You are free to reuse code or some parts in your projects. If you find errors or have interesting ideas, please comment or share them in forum.

One Comment:

  1. Can I get atmega16 code for interfacing analog joystick with uC ?

Leave a Reply

Your email address will not be published. Required fields are marked *