Using analog joystick in AVR projects

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.

joystick

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.
thumstick joystick to atmega1280

We can then write a simple program that would read joystick values and send them to a serial interface.

thumb_joystick_to_atmega1280

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.

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.

One Comment:

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

Leave a Reply