Joystick se skládá ze dvou potenciometrů, které jsou umístěny kolmo na sebe a mechanicky spřaženy. Pokud chceme zjistit, v jaké poloze se joystick nachází, stačí nám ADC převodníkem změřit napětí na středním kontaktu každého z potenciometrů. Joystick se mechanicky sám vrací do své střední polohy. Přidáme k joysticku displej SSD1306 s UTF-8 fonty.

joystick hw 504

Tlačítko na joysticku zůstane nevyužito. Pokud by ho chtěl někdo používat, stačí kontakt SW na joysticku připojit na nějaký GPIO pin a programově lze zjišťovat funkcí gpio_get( cislo_pinu ) jeho stav.

Tlačítko jde stisknout jenom pokud je joystick ve střední poloze.

Schema zapojení je skoro stejné jako u předchozího příkladu 1 (je přidán displej SSD1306 128x32)

joystick2 schema

CMakeLists.txt
cmake_minimum_required(VERSION 3.13)
include($ENV{PICO_SDK_PATH}/external/pico_sdk_import.cmake)

project(joystick C CXX ASM)
set(CMAKE_C_STANDARD 11)
set(CMAKE_CXX_STANDARD 17)

pico_sdk_init()
include($ENV{LIB_SSD1306_UTF8_PATH}/pico_lib_ssd1306_utf8_import.cmake)

add_executable(joystick
    joystick.c
)

target_include_directories(joystick PRIVATE
    ${CMAKE_CURRENT_LIST_DIR}
    ${ssd1306_utf8_SOURCE_DIR}/src
)

target_link_libraries(joystick
    pico_stdlib
    hardware_i2c
    hardware_adc
    ssd1306_utf8
    )
pico_enable_stdio_usb(joystick 1)
pico_enable_stdio_uart(joystick 0)

pico_add_extra_outputs(joystick)

Aby šel následující program přeložit, je třeba nejprve nainstalovat knihovnu (ovladač displeje) ssd1306_utf8_lib.tar.gz. Použití knihovny a postup instalace je popsán zde.

Ve smyčce (trvá přibližně 50ms) čteme nejprve polohu x z ADC0 a potom polohu y z ADC1. V programu nás nezajímá přesná hodnota napětí. V proměnných adc_x_raw a adc_y_raw budeme mít po každém čtení adc_read() >> 4 nějaké číslo z rozsahu 0 až 256. (Možná bude rozsah menší, protože se joystick nedá vytočit až do krajních poloh potenciometrů). Pokud je joystick v neutrální poloze, tak naměříme přibližně polovinu (128).

joystick.c
/* joystick.c
 * Příklad kreslení obdélníka na OLED displeji ssd1306
 * Zpracování hodnot x,y z joysticku
 * (c) Jirka Chráska 2026, <jirka@lixis.cz>
 * BSD 3 clause licence.
 */

#include "pico/stdlib.h"
#include "hardware/i2c.h"
#include "hardware/adc.h"
#include "ssd1306.h"
#include "font.h"
#include <stdio.h>
#include <string.h>

// pro OLED displej SSD1306
#define DISPLAY_WIDTH   128			// šířka displeje
#define DISPLAY_HEIGHT  32			// výška displeje
#define I2C_ADDRESS     0x3C		// i2c adresa
#define I2C_FREQ        2000000		// rychlost i2c sběrnice
// rychlost i2c sběrnice může být až 2000000
#define SDA_PIN         4			// datový pin
#define SDC_PIN         5			// hodinový pin
#define I2C             i2c0		// i2c hardware
// joystick
#define X_PIN           26
#define Y_PIN           27

// struktura pro OLED displej včetně framebufferu
static ssd1306_t disp;

extern bitmapFONT font_spleen_6x12;


// nastavení I2C sběrnice
void setup_i2c(void)
{
    i2c_init(I2C, I2C_FREQ);
    gpio_set_function(SDA_PIN, GPIO_FUNC_I2C);
    gpio_set_function(SDC_PIN, GPIO_FUNC_I2C);
    gpio_pull_up(SDA_PIN);
    gpio_pull_up(SDC_PIN);
    // pauza 200ms je nutná kvůli displeji. Pokud není, displej se někdy nerozsvítí
    // pauza 100ms je málo
    sleep_ms(200);
}

void zapnuti_displeje( void )
{
    ssd1306_init(&disp, DISPLAY_WIDTH, DISPLAY_HEIGHT, I2C_ADDRESS, I2C); // inicializace
    ssd1306_poweron(&disp); // zapnutí displeje
    ssd1306_clear(&disp);   // vymazání displeje
    ssd1306_contrast(&disp,0x50);
    ssd1306_show(&disp); // zobrazíme na displeji
}

int main()
{
char buf[80];

    memset(buf,1,sizeof(buf));
	// nastavení displeje SSD1306
    setup_i2c();
    zapnuti_displeje();
    // nastavení měniče na malý šum
    gpio_set_function(23, GPIO_FUNC_SIO);
    gpio_set_dir( 23, true );
    gpio_put( 23, 1 );
    // inicializace ADC
    adc_gpio_init(X_PIN);
    adc_gpio_init(Y_PIN);
    adc_init();
	// vymazání framebufferu
	ssd1306_clear(&disp);
	// nakreslení obdélníka z horního levého x=0,y=0
	// do dolního levého rohu x=127,y=15
    ssd1306_draw_empty_square(&disp, 0, 0, 127, 15);
	// nakreslení obdélníka z horního levého x=0,y=16
	// do dolního levého rohu x=127,y=31
    ssd1306_draw_empty_square(&disp, 0, 16, 127, 15);
    ssd1306_draw_line(&disp, 63, 1, 63, 30);

    ssd1306_show(&disp); // zobrazíme na displeji

    while(1) {
        adc_select_input(0);
        uint adc_x_raw = adc_read()>>4;
        adc_select_input(1);
        uint adc_y_raw = adc_read()>>4;
        const uint bar_width = 128;
        const uint adc_max = (1<<8) - 1;
        uint bar_x_pos = adc_x_raw * bar_width / adc_max;
        uint bar_y_pos = adc_y_raw * bar_width / adc_max;
        
        ssd1306_clear(&disp);
        ssd1306_draw_empty_square(&disp, 0, 0, 127, 15);
        ssd1306_draw_empty_square(&disp, 0, 16, 127, 15);
        ssd1306_draw_line(&disp, bar_x_pos, 1, bar_x_pos, 15);
        ssd1306_draw_line(&disp, bar_y_pos, 16, bar_y_pos, 31);
        // zpracování x souřadnice joysticku
        sprintf(buf,"x=%d",bar_x_pos);
        if( bar_x_pos == 64 ) {
            ssd1306_draw_utf8_string(&disp, 100,  2, &font_spleen_6x12, buf);
        }
        if( bar_x_pos > 64 ) {
            ssd1306_draw_square(&disp, 64, 0, bar_x_pos - 64, 16);
            ssd1306_draw_utf8_string(&disp, 10,  2, &font_spleen_6x12, buf);
        }
        if( bar_x_pos < 64 ) {
            ssd1306_draw_square(&disp, bar_x_pos, 0, 64 - bar_x_pos, 16);
            ssd1306_draw_utf8_string(&disp, 100,  2, &font_spleen_6x12, buf);
        }
        // zpracování y souřadnice joysticku
        sprintf(buf,"y=%d",bar_y_pos);
        if( bar_y_pos == 64 ) {
            ssd1306_draw_utf8_string(&disp, 100, 18, &font_spleen_6x12, buf);
        }
        if( bar_y_pos > 64 ) {
            ssd1306_draw_square(&disp, 64, 16, bar_y_pos - 64, 16);
            ssd1306_draw_utf8_string(&disp, 10,  18, &font_spleen_6x12, buf);
        }
        if( bar_y_pos < 64 ) {
            ssd1306_draw_square(&disp, bar_y_pos, 16, 64 - bar_y_pos, 16);
            ssd1306_draw_utf8_string(&disp, 100, 18, &font_spleen_6x12, buf);
        }
        ssd1306_show(&disp);
        sleep_ms(5);
    }

    ssd1306_poweroff(&disp);
    // ukončení práce s displejem (uvolnění paměti framebufferu)
    ssd1306_deinit(&disp);
}

Nepříjemnou vlastností je to, že i když s nebudeme s joystickem hýbat, tak budou naměřené hodnoty poskakovat. Je to způsobeno jednak nepřesností samotného ADC převodníku a dále nestabilitou referenčního napětí 3.3V.

Nestabilitu referenčního napětí můžeme zmenšit přepnutím PWM měniče to neúsporného režimu. Dělá se to takto:

// nastavení měniče na malý šum
gpio_set_function(23, GPIO_FUNC_SIO);
gpio_set_dir( 23, true );
gpio_put( 23, 1 );

Další zlepšení může být třeba tak, že snížíme rozlišovací schopnost ADC převodníku, uděláme místo 12 bitového ADC jenom osmibitový. Tím se sníží přesnost i citlivost a zároveň se potlačí šum (ono poskakování).

adc_select_input(0);
uint adc_x_raw = adc_read()>>4; // použijeme jenom 8 horních bitů
adc_select_input(1);
uint adc_y_raw = adc_read()>>4; // použijeme jenom 8 horních bitů

IMG 20260508 144047

IMG 20260508 144104

IMG 20260508 144106

IMG 20260508 144113

IMG 20260508 144118