In many cases, the joystick manipulator is the best choice for user input. Whether it is a game, robot, or flying machine – a joystick is the most intuitive way of controlling them. You can actually find them in gaming controllers like PlayStation or XBOX. The one we are going to the interface is Thumb Joystick I purchased some time ago from SparkFun. They are really cheap, and as users report, it is practically the same as in XBOX 360, which can be replaced if one is broken.
I didn’t bother making a PCB for it – I 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. The push-button is simply an action button that can be activated by pressing the joystick down. So controlling joystick is a matter of analog read of both potentiometers with microcontroller ADC inputs.
The joystick has sprung to return the thumbstick into the 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 the center point at analog value 512.
Connecting joystick to Atmega1280
Since I have a couple of Arduino boards, I will use the Arduino Mega128 board, but as a regular AVR development board, I will program using AVRStudio6.1 and C language. The board has everything needed, including a Serial to USB interface based on FT232RL, power supply, and all pins available for interfacing. In our demonstration, we are going to connect the joystick to the microcontroller as follow:
- Both potentiometers side pins to VCC and GND;
- the center pin of vertical axis potentiometer to ADC0;
- the center pin of horizontal axis potentiometer to ADC1;
- push button to PortE pin 4.
We can then write a simple program that would read joystick values and send them to a serial interface.
Coding AVR to read the joystick
To read joystick analog values and send data through the serial interface, we will need ADC and USART routines. For ADC, we will need only two functions – one to initialize ADC and the 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 the channel and make a single conversion that returns a 10 bit ADC value.
As for USART communications, we 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 below.
In our code, we will loop through ADC reads, interpret them as speed values and then output them to the terminal screen. The speed value will vary depending on how much joystick is pushed. The more it is pushed to the limit, the bigger the speed value should be in a 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 into the algorithm.
void JoystickCalibrate(void)
Normally you would expect a potentiometer at the center to result in an 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 a vertical center value of 498 while horizontal 506. this may lead to some errors in precise control algorithms. I measured min and max values during the calibration routine, including the center potentiometer value, and stored them in EEPROM memory. It has to be done once on the first program run. Later program checks if the calibration was 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 the calculation of speed and terminal messages are only to illustrate how things work.
You are free to reuse code or some parts in your projects. If you find errors or have interesting ideas, please comment.
Can I get atmega16 code for interfacing analog joystick with uC ?