TinyGo - Joystick et boutons

Toute cette série est bien jolie, mais une console de jeu sans moyens d'interaction, c'est pas génial.
Je vous propose aujourd'hui de voir comment contrôler le joystick et les boutons de la Pygamer.

Je rappelle ici le disclaimer de l'article sur les fontes : la partie graphique va nécessiter un peu de travail, l'usage de tinyfont ou tinydraw ne fournira pas les performances nécessaires. Des projets ont tenté des approches, il faudra aller les analyser pour voir ce qui est faisable. Je pense au projet GoBadge, destiné au Pybadge, qui a plein de points communs avec la Pygamer, ou au projet Xship qui propose un jeu sur Pygamer, mais je ne l'ai pas encore essayé.
Merci d'ailleurs à Daniel Esteban et Jean-Yves Pellé pour ces 2 projets qui m'ont permis de comprendre beaucoup de choses.

Mais sans plus attendre, le code du jour :

package main

import (
	"image/color"
	"machine"
	"strconv"

	"tinygo.org/x/drivers/st7735"
	"tinygo.org/x/tinyfont"
	"tinygo.org/x/tinyfont/freemono"
)

var black = color.RGBA{0, 0, 0, 0}
var red = color.RGBA{255, 0, 0, 255}
var gray = color.RGBA{200, 200, 200, 255}

func main() {
	buttons := NewButtons()
	buttons.Configure()

	joystick := NewJoystick()
	joystick.configure()

	display := st7735.New(machine.SPI1, machine.TFT_RST, machine.TFT_DC, machine.TFT_CS, machine.TFT_LITE)
	machine.SPI1.Configure(machine.SPIConfig{
		Frequency: 16000000,
		SCK:       machine.SPI1_SCK_PIN,
		SDO:       machine.SPI1_SDO_PIN,
		SDI:       machine.SPI1_SDI_PIN,
	})
	display.Configure(st7735.Config{Rotation: st7735.ROTATION_90})
	display.FillScreen(black)

	var buttons_pressed = ""
	var x_axis uint16 = 0
	var y_axis uint16 = 0
	for {
		x_axis = joystick.getX()
		y_axis = joystick.getY()
		buttons_pressed = ""
		pressed, _ := buttons.Read8Input()
		if pressed != 0 {
			println(pressed)
		}
		if pressed&machine.BUTTON_A_MASK > 0 {
			buttons_pressed += "BTN A"
			println("BTN A")
		}
		if pressed&machine.BUTTON_B_MASK > 0 {
			buttons_pressed += "BTN B"
			println("BTN B")
		}
		if pressed&machine.BUTTON_SELECT_MASK > 0 {
			buttons_pressed += "SELECT"
			println("SELECT")
		}
		if pressed&machine.BUTTON_START_MASK > 0 {
			buttons_pressed += "START"
			println("START")
		}

		display.FillScreen(black)
		tinyfont.WriteLine(&display, &freemono.Bold9pt7b, 10, 20, strconv.Itoa(int(x_axis)), gray)
		tinyfont.WriteLine(&display, &freemono.Bold9pt7b, 10, 50, strconv.Itoa(int(y_axis)), gray)
		tinyfont.WriteLine(&display, &freemono.Bold9pt7b, 10, 80, buttons_pressed, gray)
	}
}

Cette fois, ça fait un fichier plus conséquent et en plus il y a un fichier à côté pour aider à simplifier la gestion des boutons et du joystick.
Je vais tâcher d'expliquer au maximum tout ce qui se passe.

Commençons par les boutons.

buttons := NewButtons()
buttons.Configure()

Vous voyez comme c'est simple ? ;)
Le code nécessaire se trouve bien sûr dans pygamer.go et une version brute de ce qui est appellé serait en fait :

var buttons = shifter.New(shifter.EIGHT_BITS, machine.BUTTON_LATCH, machine.BUTTON_CLK, machine.BUTTON_OUT)

Vous pouvez regarder le code entier, mais à part déclarer la fonction NewButtons et gérer le retour d'un objet device que nous stockons dans la variable buttons, il n'y a rien de plus.
Vous voyez qu'il s'agit d'utiliser le driver tinygo.org/x/drivers/shifter qui va gérer le composant qui permet d'accédr à l'état des boutons de la Pygamer.
Ce type de composant permet de ne pas monopoliser autant de broches du micro contrôleur qu'il y a de boutons.

Il suffit alors de récupérer l'état de ce composant :

pressed, _ := buttons.Read8Input()

La variable contient alors une information qu'il faut décoder à l'aide de masques logiques. Je vous laisse faire des recherches sur les registres à décalages ( = Shift registers => shifter) mais il y a un article assez complet sur Electronique amateur.

if pressed&machine.BUTTON_B_MASK > 0 {...

C'est ce que l'on fait par cette ligne qui sert ici à savoir si le bouton B est appuyé.
Notez que l'on peut tester l'appui simultanné de plusieurs boutons, la variable pressed contient alors une valeur cumulant les différents bits correspondants.

Pour la partie joystick, nous allons aussi décortiquer ce que signifie les lignes :

joystick := NewJoystick()
joystick.configure()

Le code brut si on se passait du fichier pygamer.go serait alors :

type joystickStruct struct {
	x machine.ADC
	y machine.ADC
}
// équivalent à joystick := NewJoystick()
joystick := joystickStruct{x: machine.ADC{machine.JOYX}, y: machine.ADC{machine.JOYY}}

// équivalent à joystick.configure
joystick.x.Configure(machine.ADCConfig{})
joystick.y.Configure(machine.ADCConfig{})

Pour rassembler la logique, on utilise une structure qui contiendra en fait les 2 axes du joystick.
Ces 2 axes sont des composants générants des valeurs analogiques qui ont ensuite besoin d'êtres converties en valeurs numériques pour que nous puissions les utiliser.
Cela passe par une couche d'abstraction matérielle ADC (Analog to Digital Conversion).
machine.JOYX et machine.JOYY sont les 2 objets définis dans TinyGo pour Pygamer pour représenter les 2 axes.

Nous pouvons ensuite lire les valeurs de ces axes :

x_axis = joystick.getX()
y_axis = joystick.getY()

Ce qui correspond au code :

x_axis = joystick.x.Get()
y_axis = joystick.y.Get()

Le reste du code est ma tentative pour afficher les informations récupérées.
Vous pouvez constater qu'il y a un phénomène de clignotement qu'il faudra gommer par la suite par un usage différent des ressources graphiques.

Sorry, your browser doesn't support embedded videos.

Vous en avez maintenant l'habitude, cet exemple est disponible dans le repository dédié aux exemples TinyGo.

Merci, n'hésitez pas à me faire parvenir vos remarques par email ou par le biais d'issues sur le repository.