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.

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.

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ů




