Tag Archives: Lua

Quelques essais avec le moteur de jeu LÖVE en Lua

* This post is also available in English.

Dessiné avec Pencil2D et MyPaint, animé en utilisant un maillage dans le moteur de jeu LÖVE (enlangage Lua), J’ai également fait quelques essais en C avec libSDL, mais cela ma paraît plus rapide de prototyper en Lua avec LÖVE, quitte à ajouter des bibliothèques en FFI pour les parties ayant besoin de beaucoup de calcul (dans cet exemple, les calculs sont légers et GL est utilisé pour le rendu), ou de le porter en C/SDL dans un second temps. Tous ces logiciels sont des logiciels libres, utilisés sur un système GNU/Linux.

Ce premier exemple utilise un simple maillage sur une image, et la déforme en utilisant des mouvement circulaires, comme je l’avais fait sur TIC-80 en juillet 2021.

Voici l’image utilisée comme texture du maillage, réalisé avec la branche MyPaint de Pencil2D et affiné avec MyPaint lui même :

Voici une version modifiée avec un crâne rigide, comme il est anthropomorphe. Je garde les 2 versions de l’algorithme le premier restant intéressant pour représenter de façon plus réaliste des invertébrés.

Cet autre test agglomère différentes tests d’ombrage (shaders). J’applique un seuil sur certains, au dessus de ce seuil une couleur apparaît et en dessous une autre couleur, sans ce seuil il y aurait des dégradés. Cela permet d’avoir des effets 2 couleur dans un style très épurés. J’ai mis le code source de ces ombrages pour LÖVE à disposition sur Framagit (Fichier demo01.love depuis ce site).
.

Cet autre test utilise un maillage sur une image en 2 étapes pour la texture. Ces images sont interchangées permettant l’animation de la bouche. Contrairement à la première animation, il ne s’agit pas d’une déformation utilisant des fonctions sin() de façon relativement homogène et indépendante sur la courbe. Le maillage est courbé en incrémentant l’angle à chaque étape de sa construction. Une petite onde sinusoïdale est ajoutée pour simulé un déglutition au travers du corps, à la façon de serpents qui avalent une grosse proie.

Le liquide que le monstre rejette est une autre texture. Celle ci est copiée sur un Canvas (toile) intermédiaire, afin de permettre une rotation cyclique le long de la texture (comme un scrolling sur un ruban), Cette partie ma été inspiré par les boss de fin de niveau du jeu « Conan Chop Chop ». Mais j’ai amélioré un peu le principe : Un Ombrage est alors ajouté sur le canvas au moment dfe l’afficher à l’écran, il zoom le jet sur sa longueur et ajoute un légère onde sinusoïdale. L’éclaboussure à la fin est la superposition de plusieurs fois la même animation en 3 étapes faite sous Pencil2D.

Voici le monstre dessiné rapidement avec seulement 2 étapes, pour la mise à l’épreuve du concept. Cela pourrait être optimisé sur disque en n’ayant que les 2 têtes dans le fichier et en les dupliquant dans la texture au chargement de l’image, LÖVE ne me semble pas aussi pratique que libSDL pour les maillages texturés :
bidule spritesheet

L’image utilisée comme texture pour la partie sur laquelle est appliquée l’ombrage :
flux de vomit intense

L’éclaboussure au bout du jet, chaque partie est affiché séquentiellement en boucle, et a été animé à l’aide de Pencil2D (branche MyPaint):
Éclaboussure de vomit

L’animation est d’abord réalisée avec Pencil2D, puis sauvegarde avec une image (PNG avec transparence) par image temporelle, puis à l’aide de la commande « montage » du paquet ImageMagick assemblé dans une feuille de sprite de la façon suivante:

montage sprite0001.png sprite0006.png sprite0010.png -tile 3x1 -geometry 64x128 spritesheet.png

Mise à jour du 19 mars :

En se basant sur les mêmes méthodes, j’ai amélioré le code pour gérer des animations dessinées à main levée, de manière un peu plus générique, comme texture d’un maillage. J’ai donc fait un croquis animé de l’ouverture d’une fleur pour tester ce principe.

Fleur qui s'ouvre

Elle comporte 8 images distinctes, l’ordonnancement des images, dont le temps d’apparition peut varier pour la dynmique est gérée via une table comportant le nombre d’image temporelle par image :

table = {1,1,2,2,2,3,3,4,4,4,5,5,6,6,7,8,8,8,8,8}

Je peux cycler facilement dans la table en utilisant une fonction du type math.floor( (time()*fps) % #table) ou juste lire la table pour une animation unique, pulmonique a une fleur qu’il s’ouvre ici.

Je l’ai donc utilisé comme élément décoratif. Maintenant que je vois que cela fonctionne, l’animation peut être affinée, et le code également pour être encore plus générique et avoir le besoin d’ajouter de moins en moins d’information pour ajouter rapidement de nouvelles animations.

Après être satisfait du premier jet de l’animation au sein du programme, j’ai un peu plus nettoyé, amélioré les couleurs, et me suis aperçu qu’il manquait une frame dans mon animation, la dernière était dupliquée de l’avant dernière. Dans tous les cas, il faut que je supprime l’espace inutile et que je sépare la queue de la fleur, la feuille de sprite en png fait 180~200 Ko (150 Ko recompressée avec zopflipng), c’est trop gros, multiplié par le nombre d’image et d’objets on peut rapidement se retrouver avec des dizaines de Mo de PNG sur disque et encore pire en RAM, de quoi exploser tous les caches. Je pense donc que je vais séparer la fleur de sa tige, et essayer de la cadrer plus précisément, un 64×64 devrait faire l’affaire.

Fleur qui s'ouvre améliorée

 »’Mise à jour mai 2022  »’
* Vous pouvez tester la dernière version et voir les sources à l’adresse https://framagit.org/popolon/reforest
* Les fichiers LÖVE de la dernières version, prêts à l’emploi, sont disponibles à l’adresse https://framagit.org/popolon/reforest/-/releases (Version 0.2 depuis ce site).

capture d'écran de Reforest 0.2

Playing with button and LEDs on RISC-V based ESP32-C3 NodeMCU board with ESP-IDF (FreeRTOS)

Table of Content


* Introduction
* Circuit
** Components
** Breadboard
** Choose GPIO ports and their board pins

** LED part
** Resistors
** Switch button part
* The Software
** Initialisation
** Main loop
** ISR (Interrupt Service Routine)
** Debouncing
*** ESP timer

Introduction

Update: I wrote, following this one, another article to teache the usage of a potentiometer and an OLED screen..

After ArchLinux upgrade from python 3.9 to 3.10, tools need to be reinstalled by:

cd ~/esp/esp-idf
git pull
git submodule update --init --recursive
./install.sh esp32c3

If you never used ESP-IDF, you can read the previous introduction article to ESP-IDF on RISC-V based ESP32-C3, how to install it and start environment for compiling and flashing code. I also wrote article about using ESP32-C3 with Apache NuttX POSIX OS, but it will be useless here.

This article is about, on ESP32 (more specifically a less than 3.5€ ESP32-C3 based NodeMCU board, but it should work about the same way on other ESP based boards) :
* How to blink an external LED using GPIO, including how to know LED needed voltage, amperage, and compute needed resistor, with several possible means.
* Explanations about resistors values colours bands and computation of parallel mounted resistors. I also give link to free and open source softwares I wrote to help to compute resistors (depending on led, and desired intensity).
* How to connect an external switch to GPIO, and which resistor is needed. How to receive and manage it’s state a good way. By debouncing physical human pressure on switch, and use software interruption (that’s more easy that it could sounds).
* How to blink included RGB LED and stop/start it by using switch, an asynchronous way.

Hardware: The Circuit


After 20 years without practising electronics and searching about how to make the circuitry, for the LED and for the button, I found several one for each part. I finally found a some article that give explanations for this board, but using Arduino, and with lot of deep errors. Too strong resistor, not at the good place, after gathering of informations of lot of sources and after made lot of tests and looking back to source, I decided to write a complete tutorial for real beginners like me. This could help me to understand again all needed bases when needed, and I hope it will be useful for other people too.

Components

A basic tool to test and learn on circuits is a breadboard, it allow to test without needing to solder anything. It can so also be used by children, as 3,3 V is not dangerous at all.

* We need also a 5mm LED, that can be found on old electronics circuit or are really cheap, we use a RED that is perfect to represent an ON or OFF state and with 1,8V can be managed by 3,3V board.
* We also need a switch button. A cap is more comfortable but not required to make it work.
* We also need a resistor, if you have a 75 ohms (75Ω), or a 100Ω + a 330Ω, that’s perfect, else a 100 ohm alone is just nice, above 100 and until 330Ω there will still have light, but it could not be very bright.

You can also use a 10KΩ resistor for the switch, called pull-up (or pull-down) to have a more perfect signal, but external resistor is not required for this kind of board, as it includes internal pull-up/pull-down resistors, that is more efficient and possible to enable or disable by software with the esp_err_t gpio_set_pull_mode(gpio_num_t gpio_num, gpio_pull_mode_t pull). We will not need to use the Open drain mode, used with SPI, where pull-up/pull-down is used as changing active/inactive signal.

Breadboard

half width Breadboard
Breadboard is a very convenient board that allow to test circuits without soldering. Any kind of components can be easily inserted and removed, and contact just work. The holes follow the weird electronics standard space of 2.54mm. This is because 2.54 mm is 1 inch of the imperial units, as modern electronics was made in the US, and this is the only one country that still uses this very complex units system. System International (SI), made during French Revolution, is far easier for any kind physics computation.


half width Breadboard with electric links drawn
In brown, electric links hidden inside breadboard

The plastic made board contains underlying electric links. They are divided on three parts, large central part and two side part.
* The two side part are just long line generally used for Ground (GND) and Voltage (V or Vcc). Blue (for GND) and red (for Vcc) lines are drawn to help to avoid to mix both VCC and GND on the same line and so avoid short circuits. Having one VCC+GND backbone one each side is just perfect for microcontrollers that can have both 5V and 3.3V or to have easier access to each side of the board. More efficient boards only use 3.3V.
* In the middle part, electric links are perpendicular to the side ones, separated in the middle, and are used to place electronics components. Some hole counters every 5 holes, and Letter at both extremities of the breadboard are wrote to copy a circuit from a board map an easier way.

Choose GPIO ports and their board pins

Here is the pineout of the board, we need to choose ports used for the circuitry, some pins have GPIO including only digital signal (0 or 1), analog support with analog to digital conversion (ADC) input, or digital to analog conversion (DAC) output. Some pins are to add voltage to electronic components (3,3V ports in RED on the picture) Warning to not connect them directly to GPIO port, you could definitively damage your board..


NodeMCU ESP32-C3-32S-kit pineout
NodeMCU ESP32-C3-32S-kit pineout

Here is the pinout mapping for NodeMCU ESP32-C3-32S-kit. Drawing, thanks to J-C François.

You can generally found them in the documentation of your board. founding this kind of schemas is not always easy, but they are often provided y board vendors. List of ESP32-C3 SoC GPIO are on the doc, but every board vendors can choose to map or not them on the board pins.

We choose here GPIO 1 (GPIO_NUM_1 in the API) for the button, and GPIO 2 (GPIO_NUM_2) for the LED. For this specific board, they are respectively second and third pins, on the left side starting from the top.

ADC0_CH1, GPIO1 and GPIO2
In the source code, as we will see later:

#define EXTERN_BUT GPIO_NUM_1 // BUTTON on GPIO1
#define EXTERN_LED GPIO_NUM_2 // LED on GPIO2

LED part

Anode (+) and cathode (-) on a diode symbolWe will use here a red LED. LED means « light-emitting diode », this is so a special kind of diode that emit light. A diode work only in one direction, start at a determined voltage, and shouldn’t be powered over a top voltage to avoid damages. If it is powered in the wrong direction (and not too much), nothing happen.


Diode and LED symbols
Diodes (at left) and LED (at right, with arrows) symbols.


Anode leg is longer than cathode one
Anode leg is longer than cathode one, the leds rolled by themselves on the flat side.


led bottom ring is flat at the cathode side
led bottom ring is flat at the cathode side

For LED part we need a 330Ω resistor. electronics LED work with different ranges. About 10 to 50mA, 20 to 30mA is generally safe, but warning, sometime less than mA. Generally with lower value than 20mA, the LED will not be very bright. Voltage depend on LED colour. Try to keep in the middle of these values to have enough light and to not burn your LED. The best is to follow the specifications of your LED vendor.
* IR 1,2 to 1.6V
* Red 1,8 to 2.1V
* Orange/yellow 1.9 to 2.2V
* Green 1.8 to 3.1V
* White/UV 3 to 3.4V
* Blue 3 to 3.7V
* RGB, each colour pin has his own colour voltage.

You can test the « Forward Voltage » of a LED with a digital multimeter. Forward because this is the direction where it lights.


Digital multimeter in diode testing position
Digital multimeter in diode testing position

Select the diode mode of the multimeter (as on above picture), and touch the Anode (longer leg, to the rounded side of the LED base) with the red connector, and the cathode (shorter leg, to the flat side of the LED base) with the black connector.

We use a red LED, that consume about 1,8V. The ESP32C3 has 3.3V output, so there is a difference of :

3.3 - 1.8 = 1.5V

We need a resistor to avoid an over-voltage of the LED.

Resistors

After Ohm law, U = RI, where U is voltage (V), R=resistance (Ohms or Ω) and I intensity (A). So :

R = U/I
 1.5/0.02 = 75Ω


We choose the nearest resistor equal or above this result.

The resistors are painted with rings indicating their resistance. There is in general 4 or 5 rings (or bands). That can be read from left to right as: resistance (2 rings), multiplier (one ring) and tolerance % (1 or 2 rings). Here is a good 5 bands calculators.

A good mnemonic-technique to memorize colours order in English is "Bad Beer Rots Our Young Guts But Vodka Goes Well (in) Silver Goblets". for value rings they start by 0, then 1, etc..., for multiplier by 1, then 10, etc....

Black 0 0 x1
Brown 1 1 x10 ±1%
Red 2 2 x100 ±2%
Orange 3 3 x1000=x1K
Yellow 4 4 x10K
Green 5 5 x100K ±0.5%
Blue 6 6 x1000K=x1M ±0.25%
Violet 7 7 x10M ±0.1%
Gray 8 8 x100M ±0.05%
White 9 9 x1000M=1G x1
Gold ±5%
Silver x0.01 ±10%

Warning, to the light when you look at the bands, some bands can be confused, and it can have disastrous consequences, especially if that's for the multiplier.


Resistors with flash at left and ambient light shadow at right
Resistors with flash at left and ambient light shadow at right. As we can see on the light blue resistor at left, the second painted ring is orange, where it seems brown with the shadow of the natural light et right.

If you have some doubt about their value, you can still use a digital multimeter on their resistance position displayed by a greek Omega character (Ω).


Digital multimeter in resistor testing position
Digital multimeter in ohm (resistor) testing mode position

Some suggest 330 ohms with 5V, this is really too much, and even for 3.3V, that result in

1.5/330 = 0.01A = 1mA

1mA is 1/20th of ideal light, this is still light up but with far less intensity.

I have a 47ohms resistor that would result in a too bit too high value:

1.5/47 = 31.91mA

See this video (the difference is visible on the ambient light, as camera focus on the light. Here 330 ohms (resulting to 1mA) and 100 ohms (resulting to 15mA) resistors are connected in parallel, a better solution (see below), and then 330 ohms only, we can see the ambient light change only as phone sensor wasn't in HDR mode. This difference of light means that the LED is not powered enough.

I made a simple resistor calculator tool with TIC80 in Lua You can use it online or download it to use on your computer or phone, it's Open Source with GPLv3 license:



Screenshot of resisor_calculator

28 december 2021 update: I discovered after this post that the famous free and open source electronic design suite, KiCad contains a tool called PCB Calculator that allow to compute lot of electronics related things, including resistors, and has a resistor color table. The version 6.0.0 of Kicad had been released on 25 december and contains several years of work huge improvements.

Another Ohm law say that When linking resistors in parallel called "parallel_resistor.lua", the computation is:

1/R = 1/R₁ + 1/R₂ + ... + 1/R₉ + ...

So

R = 1 / (1/R₁ + 1/R₂ + ... + 1/R₉ + ...)

And we have with 330Ω and 100Ω resistors in parallel:

R = 1 / (1/100 + 1/330) = 1 / 76.744Ω
I = 1.5/76.744 = 0.01954A = 20mA

That's just near perfect.

This formula make everything in one path:

I = U * 1 / (1/R₁ + 1/R₂ + ... + 1/R₉ + ...)

So here:

1.5 / (1/100 + 1/330)

I also made a simple command line calculator tool for resistors in parallel, available here https://framagit.org/popolon/electronhelp beside the tic80 single resistor calculator. I still need to implement, parallel calculation in tic80 version, and unique resistor calculation in command line version.

Switch button part

Pull-up resistor
For the button, the principle is just a switch, that cut current by default and make a short-circuit when the button is pressed. This switch is on a circuit between one of the GPIO pin and the ground (GND) pin. Due to the signal noise, a pull-up/pull-down resistor is needed to stabilize the signal. Pull-up/pull-down resistor is just a very low current that goes out of short circuit current. A 10KΩ resistor connected to the 3.3V pin is used for this. It is called pull-up if the resistor is connected to the button circuit between the button and the GPIO (3.3V too) pin, or pull-down if it is connected between the button and the ground.

Today most MCU boards, including ESP32-C3 include internal pull-up/pull-down resistors. They can be activated by software.

The legs connexion inside switch should be counter-intuitive at first, but legs on the same side are unconnected, each leg is connected with the one on the opposite side. To be clear, on the schema about the pull-up resistor, the two left legs are always connected together and the the two right legs are connected together, the button make the connection between left legs and right legs.

The Software

We want to manage:
* A main loop that blink the RGB LED of the board
* An external red LED that light when blinking is off.
* A switch button, that can at anytime, asynchronously, switch between these two states. This button can't be locked down, so we change state at each time it is push down. Releasing it doesn't have any effect.

To manage all this an asynchronous way, we need interrupts.
* Timer interrupt for waiting between LED blink steps
* Interrupt when on/off switch is pushed down, to change state.
* At this level we need also another asynchronous timer interrupt used for what is called debouncing. When the button is push down, it physically bounds, and the contact is on/off several time. The same effect is produced at the electrical level but the analogue current, is managed by electronic components, we can see at our level the current with just digital logical 1 (on) or 0 (off) state.

There is an included example with queueing in ESP-IDF, but it was not very clear for me about it's goal. After managing the switch, I looked it again, it allow to test queuing and interrupt, just by connecting 2 pins to 2 other pins, two pin send signal, the two other receive it, following by serial terminal output to understand what is running, is also of great value. Anyway, after searching a lot about how to use switch button, I found this topic helped me for the software part but was not complete, especially for debouncing. So I wrote here a working one and how it works. All the functions about GPIO of ESP-IDF are described here.

I made a copy of the esp-idf/examples/get-started/blink example as a starting project. See the previous article about ESP-IDF for this and how to compile it.

Just copy the blink example, and replace blink/main/blink.c by my version, that you can download here.

You can as an helper compile and flash it a first time, you will then only have to relplace the blink.c file and compile and flash it again.

To initialise the environnement for an ESP32-C3:

. $HOME/esp/esp-idf/export.sh
idf.py set-target esp32c3

Then to build and flash it (you can only apply build of flash depending on your needs) :

idf.py build flash

Initialisation

Set the state = 1 meaning internal LED will blink and RED Led the opposite way will be off.
* Choose used GPIO, reset all GPIO, set LED ones as output, button as input
* Initialise interrupt and timer fur button, we set it as POSEDGE (positive edge), this is the time when the current move up from 0 to 1, when liaison contact become active inside button.

In the default blink project, the blue intern led is defined as BLINKING LED, we instead start from it's internal GPIO, and the same for other internal colours of the internal RGB LED (respectively R=3. G=4. B=5). I use here connector GIO1 pin for the button and GPIO2 for the external LED (line 19).

#define RED_GPIO   GPIO_NUM_3
#define GREEN_GPIO GPIO_NUM_4
#define BLUE_GPIO  GPIO_NUM_5

#define EXTERN_BUT GPIO_NUM_1 // BUTTON on GPIO1
#define EXTERN_LED GPIO_NUM_2 // LED on GPIO2

We define a variable called state that define the current state of the machine at 1. 1 = on, 0 = off (line 31):

// set initial state to blinking led on
static int state = 1;

We need to first reset all the pin we use (line 61):

  gpio_reset_pin(RED_GPIO); // reset internal RGB LED GPIO
  gpio_reset_pin(GREEN_GPIO);
  gpio_reset_pin(BLUE_GPIO);
  gpio_reset_pin(EXTERN_LED); // reset external GPIO
  gpio_reset_pin(EXTERN_BUT);

We then create the timer, by sending to the appropriate function the previously defined structure (line 67).

  // create the timer
  esp_timer_create(&debounce_timer_args, &debounce_timer);

We set all the characteristics of the button GPIO. Could be set outside of the code, and send it to the gpio_config function (line 70).

  if (ISR_MODE == 1) {  // Interrupt mode
    gpio_config_t btn_conf;
    btn_conf.intr_type = GPIO_INTR_POSEDGE;  // Interrupt at Posedge only
    btn_conf.mode = GPIO_MODE_INPUT;           // Use INPUT mode
    btn_conf.pin_bit_mask = EXTERN_BUT_MASK;   // MASK of the button pin
    btn_conf.pull_up_en = GPIO_PULLUP_DISABLE;    //Disable pullup
    btn_conf.pull_down_en = GPIO_PULLDOWN_ENABLE; //Enable pulldown
    gpio_config(&btn_conf); // send config
  } else {               // Naive mode
    gpio_set_direction(EXTERN_BUT, GPIO_MODE_INPUT);
  }
  printf("button configured\n");

We now set the interruption associated with the button at the GPIO EXTERN_BUT to the isr_button_pressed() handler function (line 83).

  if (ISR_MODE == 1) {
    gpio_install_isr_service(ESP_INTR_FLAG_DEFAULT); // install GPIO interrupt
    gpio_isr_handler_add(EXTERN_BUT, isr_button_pressed, (void*) EXTERN_BUT); //Add handler of interrupt
    printf("Interrupt configured\n");
  } // end of ISR initialistaion

Then we set the OUTPUT direction of the LED and change the external red LED to 1 - state = 0, or off, we don't set the 3 internal LED at off, as they will be set just after, at the beginning of the loop (line 89)

  /* Set the LED GPIO as a push/pull output */
  gpio_set_direction(BLUE_GPIO, GPIO_MODE_OUTPUT);
  gpio_set_direction(RED_GPIO, GPIO_MODE_OUTPUT);
  gpio_set_direction(GREEN_GPIO, GPIO_MODE_OUTPUT);
  gpio_set_direction(EXTERN_LED, GPIO_MODE_OUTPUT);
  printf("LED output configured\n");

  gpio_set_level(EXTERN_LED, 1 - state); // 1-1=0 1-0=1

Main loop

In this infinite loop (while(1)), we basically light one colour of the LED, wait a bit using interrupt, to avoid power consumption, then light off the LED. Some informations bout the current state are send to the serial console like the state of the system, the begining of the loop, and when the internal red LED is lighted on.

We choose here to make the loop and to send the state value to the LED, then to wait, then to change to 0 value the LED to stop lighting it. It would have be better to stop the loop as soon as the system is off, ant to wait to be at on again to loop.

The loop start by printing the current state and light off RGB (line 98):

while(1) {
  printf("starting cycle by turning off the LEDs\n");
  gpio_set_level(RED_GPIO, 0);
  gpio_set_level(GREEN_GPIO, 0);
  gpio_set_level(BLUE_GPIO, 0);

then if we are in naive mode without interruption get the GPIO of the button state with gpio_get_level() function, and in any case display it. This allow to see if we currently connected the button to the appropriate GPIO pin. We then wait 1 second (1000 millisecond) all LEDs off (line 104):

  if ( ISR_MODE == 0 ) { // naive mode
    state = gpio_get_level(EXTERN_BUT);
  }
  printf("state=%d\n",state); // display current state on console
  vTaskDelay(1000 / portTICK_PERIOD_MS); // wait with lights off

We then light the blue LED, wait 200 milliseconds (0,2 s) and turn it off (line 110):

  gpio_set_level(BLUE_GPIO, state); // light on blue if state up
  vTaskDelay(200 / portTICK_PERIOD_MS);
  gpio_set_level(BLUE_GPIO, 0);     // light off blue

Then we do the same with the green LED (line 114), then the red (line 118), and then the green at the same time, lighting a yellowish colour (line 122). We wait for the last time 200ms (line 124) and the cycle restart by turning off all the LEDs (line 100):

  gpio_set_level(GREEN_GPIO, state);// light on green
  vTaskDelay(200 / portTICK_PERIOD_MS);
  gpio_set_level(GREEN_GPIO, 0);    // light off green

  gpio_set_level(RED_GPIO, state);  // light on red
  vTaskDelay(200 / portTICK_PERIOD_MS);
  gpio_set_level(RED_GPIO, 0);      // light off red

  gpio_set_level(RED_GPIO, state);  // light on red
  gpio_set_level(GREEN_GPIO, state);// and green => yellow!!
  vTaskDelay(200 / portTICK_PERIOD_MS); // end loop
}

ISR (Interrupt Service Routine)

gpio_isr_register() to register interruption function.

gpio_install_isr_service() and gpio_isr_handler_add().

Interrupt allocation. One of interesting aspect to know is that you can keep interruptions in IRAM (Instruction RAM) and use datas in DRAM (Data RAM), allowing less latency in interrupt and keep them independent from flash read/write. About Memory in ESP32-C3), IRAM and DRAM can be read/write in parallel.

Example without interruption.

Debouncing


This article illustrate the problem of bouncing and 2 methods for debouncing with examples on FPGA, and that's really more simple to implement on an FPGA than on a general purpose microprocessor or microcontroller.

The main app already use the main timer vTaskDelay, so we can't use it as I first done, else it will change the return address of the function, and so break the main loop. We could create a new xApp, but the more elegant way, is to use ESP Timer, it will solve all our problems an easy way.

ESP timer

We will follow the second one, and use an High Resolution Timer, ESP Timer for this. ESP-IDF include an example in examples/system/esp_timer/. This is not that we care about high resolution, ms is good enough here, but, this has the advantage to manage the timer by interruption and to easily avoid conflicting call, as this is needed due to bounces.

We need to create the structure to be sent to the function , that initialize the interrupt, and so the header of the callback function must be predefined (line 34):

// button manager with debounce timer definition
static esp_timer_handle_t debounce_timer;
static void test_button(void* arg);
const esp_timer_create_args_t debounce_timer_args = {
  .callback = &test_button,
  /* argument specified here will be passed to timer callback function */
  .arg = NULL,
  .name = "test_button"
};

The timer function, is called by an interruption at the end of the timer. It verify if the button is still pushed, and then switch state (1-0=1, 1-0=1). The external LED is set at the opposite of the actual state, if board LED blinks, then external RED LED is off (line 44).

static void test_button(void* arg) {
  if (gpio_get_level(EXTERN_BUT) == 1)
  {
    state = 1 - state;
    gpio_set_level(EXTERN_LED, 1 - state);
  }
}

The isr_button_pressed callback function is called when the button is pushed down. (at the positive edge). Due to the bounce it can be called several times for one human analogue pressure. We still need to double check we are really at push down state (high voltage) at the end of a short delay, else goes out. We then verify that the timer wasn't started before. If it wasn't (!) the case then we start once the timer that will start test_button function after 1 millisecond (1000 microseconds). This avoid bounce but still allow quick repeated push (for fast Morse code typing, of furious action games) (line 52):

static void isr_button_pressed(void* args) {
  if (gpio_get_level(EXTERN_BUT) == 0) 
    return; // we only want to manage pushed button
  if (!esp_timer_is_active(debounce_timer)) { // start only if the timer isn't active
    ESP_ERROR_CHECK(esp_timer_start_once(debounce_timer, 1000)); // 1ms = 1Kµs
  }
}

p.s.: I found an article about another method for debouncing specifically with RTOS capabilities too.

Lua, TIC-80, LÖVE, etc : Introduction aux systèmes de particles et jeux

English version here
Code par défaut de TIC-80 au lancement

Un langage intéressant pour déveloper des jeux, contenus interactifs et de l’art procédural est le langage de script Lua. C’est un langage fonctionnel simple avec quelques fonctionnalités limitées de langage orienté objet. C’est, en raison de sa simplicité et de la compilation en bytecode au démarrage par défaut, un des plus légers et rapides langages de scripts. Différentes intégrations comme le moteur de jeu/médias et (très puissante) API LÖVE, ainsi que TIC-80, qui est un ordinateur imaginaire virtuel inspiré par PICO-8 (qui utilise également Lua). Ils permettent des prototypages rapides, pour un produit fini dans le même langage ou bien porté plus tard dans l’avancement du projet dans un autre langage. Lua est également utilisé comme langage de systèmes de plug-in (greffons), dans de nombreux jeux, outils, dont des applications de bureau (comme Blender), des applications web (comme MediaWiki derrière Wikimedia), ou dans le mondede l’embarqué. Dans ce dernier domaine, les cartes de contrôle de drones populaires et open-source (logiciels libres à source ouvertes) BetaFlight ou le logiciel de commande-radio Open-TX notamment pour les commandes Taranis. Il y a une documentation en ligne complète de Lua (en anglais) sur le site web officiel. Il est possible d’inclure des fonctions ou bibliothèques en C dans les programmes en Lua programs avec libffi. Elle a été (je crois) crée à l’origine pour Python, avec CFFI, et il a également un support FFI for PHP à présent. Il est également possible d’intégrer des scripts Lua dans des programmes en C. J’ai également découvert en écrivant cet article (merci à l’auteur de TIC-80, qu’il existe également PicoC, un simple interpréteur du langage C, permettant donc un contrôle plus bas niveau/din des structures de données. La taille du binaire est similaire à celle de l’interpréteur de Lua.

Une demo interactive du fonctionnement et utilisation s fonctions trigonométriquesDonc, après plusieurs années à regarder de temps en temps ce langage et ces outils, J’ai commencé à jouer un peu plus avec, vers la fin de 2020, et en quelques mois, je peux dire que j’ai bien progressé dans la programmation temps-réel. Que j’ai réussi à faire et même finir des jeux légers. J’ai donc du étudier de nouveau la trigonométrie de base (suivez ce lien pour explication interactive simple), de l’algèbre vectoriel de base and et quelques autres éléments amusants des mathémtiques, que je considère personnellement comme des jeux de puzzles.

Banner of Falacy Gorx, pseudo 3d game, using lot of tables
J’ai également écrit (en anglais) une court article de making-off sur Itch.io en mars, 2021, pendant une compétition de bœuf de jeu (game jam, dans le même sens que les bœufs musicaux), au lieu de coder (malgré le temps limité à 2 semaines ^^). Tout cela m’a motivé à écrire d’avantage d’articles didactiques à propos de la programmation temps-réel/vectorielle et génération procédurale. Je vais tenter d’écrire une série une série d’articles expliquant les méthodes que j’ai utilisé. Je vais tenter de le faire avec autant de simplicité pour tout le monde, mais quelques connaissances de base en programmation générale et en mathématiques pourront beaucoup aider dans ce champs du développement, comme dans la vie en général.

Je commence donc ici une courte introduction avec ce que j’ai utilisé le plus dans ces développements. Des tables d’éléments et du hazard:,z
* Les tables et leur gestion de base commune, relative à une logique d’animation
* Créer une table
* Génération procédurale du contenu d’une table
* Exemple simple pour nettoyer une table
* Variation et nettoyage d’une table en fonction de tests
* Compacter un peu le code
* Génération procédurale du contenu de la table
* Exemple simple de système de particule graphique

Les tables et leur gestion de base commune, relative à une logique d’animation

Quelques usages généraux de tables dans des applications interactives, objets, personnages, particules

La plupart des choses sont gérées, pour la scalabilitées, sous forme de tables, que ce soit les acteurs principaux (comme les personnages des joueurs), les objets, les particules, les agents actifs, etc. Les objets eux-mêmes, comportent également souvent des sous-éléments sous forme de table, pensez par exemple aux parties du corps d’une chenille.

Donc, la plupart des structures ont généralement quatre fonctions logiques vitales pouvant être gérées de différentes façons :
* Nettoyage d’une table, utilisée lorsque les éléments ne le sont plus, mais peut-être pertinent de l’utiliser également pour initialiser un état général, comme passer du menu au jeu lui même.
* Initialisation de la table, incluans généralement le nettoyage si nécessaire et l’ajout d’1 à n éléments selon les besoins.
* Mise à jour de la table, et des état de ses éléments. Cela inclus leurs relations mécaniques et physiques, leur position, leur changement d’état relatif à leur tracé à l’écran, l’interaction aveec les autres objets, et enfin leur suppression ou création d’un nouvel élément si nécessaire. Tout cela dépendant de critères très variés.
* Sortie du contenu de la table, à l’écran pour l’affichage, mais aussi du son qu’il peut produire, etc.

Les exemples donnés ici peuvent être testé avec l’interpréteur en ligne de commande de Lua, soit en tapant lua dans un shell, puis en les copie-collant, ou sans doute plus pratique, en les collant dans un simple fichier texte, par exemple, fichier.lua, puis en l’exécutant via :

lua fichier.lua

Les derniers exemples utilisent TIC-80, qui est disponible gratuitement et peut être utiliser en Web, mais également pour différents systèmes, tels que Linux, Mac, Windows, Android, Rpi, etc. Il y a une version Pro, mais je n’ai jamais utilisé ses fonctionnalités. Les exemples sont également facilement portable sur d’autres environnements.

Créer une table

On va utiliser une table Lua que l’on appelle objs[] (objs comme objets, j’aime bien mettre un s à la table et pas de s un élément unique). En Lua, il y a une particualarité par rapport aux autre langages informatique. Par défaut, les tables commencent à l’élément 1 lorsque vous les remplissez par objs={elt1,elt2}, mais il est toujours possible d’avoir un éléement ayant pour indexe 0 en utilisantpar exemple objs[0]=elt0. Il est possible de (pre-)initialiser une table video avec objs={}. Si elle n’était pas vide et que ses anciens éléments ne sont plus utilisés, le ramasse miette se chargera de les néttoyer. Ayant pas mal codé en bas niveau (C/C++ et assembleur), je ne suis pas un grand fan des ramasses miettes, mais c’est bien pratique pour les prototypes rapides.

Le contenu de la table peut être simplement affiché à l’écran avec une boucle classique for. Pour la tables appellée objs, #objs peut être utilisé pour connaître le nombre d’éléménts qu’elle contient.

objs={"boule","cube","joueur"}
for i=1,#objs do
 print(objs[i])
end

Génération procédurale du contenu d’une table

Les systèmes de particules sont utilisés ici pour les nuages, montagnes, personnages, et pour les éléments du corps du dragonEn génération procédurale, le meilleur ami du chaos, également appelé Nature, est la fonction aléatoire (random en anglais), qui génère des nombres aléatoires. Dans le cœur de Lua, la fonction math.random est dédié à ça. Elle accépte des paramètres d’étendue, limité à des entiers avec des valeurs croissantes uniquement. Nous pouvons par exemple, décider de générer un nombre compris entre 1 et 6 inclus, pour déterminer le nomrbe d’éléments que nous désirons obtgenir. Si vous essayez plusieurs fois de suite cette foncvtion, elle affichera une valeur différente comprise entre 1 et 6 à chaque fois, comme lors du lancé d’un dé à 6 faces.

print(math.random(1,6))

Dans un programme interactif ou animé, on veut généralement crée des objets aléatoires, et les faire varier suivant une certaine gestion et pendant un certain temps. Nous remplissons donc dans une tables des valeurs initiales aléatoires, qui seront ensuite réutilisées et modules. On doit donc en premier (re-)initialiser une table objs vide afin qu’elle puisse ensuite être remplie par la boucle for.

objs={}
for i=1,math.random(1,6) do
 objs[i]={lt=math.random(1,50)}
end

Nous avons donc généré ici 1 à 6 nombres aléatoires compris entre 1 et 50 et avont assigné ces valeurs à un paramètre appelé lt plutôt qu’à la table directement. Ce type d’élément est accessible de deux façons différentes en Lua, objs[i].lt ou objs[i]["lt"]. La seconde méthode peut être utilie daans certaines situations particulières, nous y reviendrons dans un autre tuto.
so now the table is filled, we can print values several times, they will be kept the same:

for i=1,#objs do
 print("objs["..i.."]="..objs[i].lt)
end

Nous utilisons ici le symbole de concaténation de chaînes de caractères (symbole ..) afin d’afficher d’avantage d’informations à propos de l’élément que nous affichons.

Exemple simple pour nettoyer une table

Il est important, lorsqu’on nettoie une table ou qu’on en retire des éléments en général, d’effectuer la boucle du dernier au premier indexe d’éléments, car lorsqu’un élément est supprimé, l’indexe des éléments qui le suivent dansla table est décrémenté, on risque donc de sauter des éléments, et de supprimer des éléments non désirés.

for i=#objs,1,-1 do
 table.remove(objs,i)
end

Dans la fonction Lua standard table.remove() (signifiant table.supprime()) les argumenst sont le nom de la table suivit de l’index de l’élément à supprimer.

Variation et nettoyage d’une table en fonction de tests

Ça peut être une bonne habitude d’utiliser une variable locale de pointeur avec un nom court pointant sur l’élément à traiter, afin de réduire le code à l’intérieur de la boucle, car on risque en général d’avoir à y accéder plusieurs fois. On supprime ici les éléments, lorsque lt (raccourcit pour lifetime, temps de vie) est arrivé à 0. Dans le cas contraire on le décrémente.

for i=#objs,1,-1 do
 local o=objs[i]
 if o.lt<=0 then
  table.remove(objs,i)
 else
  o.lt=o.lt-1
 end
end

La variable o ne peut être passer à la fonction table.remove(), car elle est utilisée comme pointeur vers unélément de la table, et nom pas comme nom de la table. Le second argument, est l’index de la table correspondant à l’éléemnt, ça n’est donc pas non plus un pointeur vers un élément.

Nous avosn à présent la base générale d’un sytème de particules.

Tous les systems de particules fonctionne avec une génération, utilisant généralement un peu de hasard et un temps de vie comme critère de base des particules. Les autres critères change plus ou moins en fonction du type de particule.

Compacter un peu le code

J’ajoute générallement les varibles ocale à la fin de la la ligne for...do afin d’avoir un code plus lisible et compacte. De la même façon je place les changements, lorsqu’ils ne sont pas trop longs derrières les mots-clés if...then ou else, afin d’avoir un code plus compacte mais toujours lisible :

for i=#objs,1,-1 do local o=objs[i]
 if o.lt<=0 then table.remove(objs,i)
 else o.lt=o.lt-1 end
end

Lua permet également d’assigner facilement un pointeur de fonction à une variable, j’utilise donc généralement la méthode suivante pour compacter d’avantage le code, comme j’utilise beaucoup l’amis chaos :

m=math rnd=m.random

Donc, ici, m est assigné à la bibliothèque math puis rnd à m(ath).random, que l’on peut décrire comme la fonction random de la bibliothèque math.

Attention : Une contrainte est de ne pas utiliser la variable m, entre sa déclaration comme équivalent de math et l’assignation à d’autres variable des fonctions m.*. Il est donc mieux de les définir en tout débbut de code pour ne pasavoir de problmes et de pouvoir utiliser librement la variable m.

Exemple simple de système de particule graphique

Dans cet exemple, nous allons simplement ajouter une distance aléatoire x (axe horizontal) et y (axe vertical) autour d’un point central, ici on choisit (120,70)

On défini donc le point central 120,70 et on varie la position de départ au hazard de -15 à +15 pixels dans chaque direction :

objs[i]={lt=rnd(10,30),x=120+rnd(-15,15),y=70+rnd(-15,15)}

Système de particule basiqueOn fera ensuite varier ces points aléatoirement dans le temps. avec un déplacement d’une distance de 0 à 1 pixel, dans les directions gauche/droite et haut/bas. On utilise donc :
Pour l’axe des x :
* -1 = gauche
* 0 = immobile
* +1 = droite
Pour l’axe des y :
* -1 = haut
* 0 = immobile
* +1 = bas

o.x = o.x+rnd(-1,1) o.y = o.y+rnd(-1,1)

Dans Lua, les fonctions sont définies par function nom_de_la_fonction(arg1,arg2,...) et se terminent par end. Dans le cas de TIC-80, par example, qui a l’avantage d’avoir toutes les fonctionnalités embarquées dans un seul binaire executable, function TIC() est une fonction appelée périodiquement, à chaque rafraichissement d’écran (nouvelle image à l’écran), permettant ainsi d’avoir du cotenu lié au temps (donc de l’animation) facilement. La focntion cls(couleur) y est utilisé pour nettoyer l’écran avec une couleur particulière avec l’inedex de couleur, « couleur », comme TIC-80 utilise une pallette de 16 couleurs indexées sur un total de 16 millions (8bits=256 niveaux pour chaque composante, Rouge, Vert et Bleu. 8^3 ~= 16 millions de couleurs).

l’emplacement des particules est tracé ici par la fonction circ() (raccourcit pour cirle, signiant cercle en anglais).

Définition de la fonction :

circ(centre X, centre Y, rayon, couleur)

On place donc le centre du points aux coordonnées de l’objet, le cercle à un rayon de 1 et utilise l’index de couleur 0,; qi est noir par défaut sous TIC-80.

circ((o.x, o.y, 1, 0)

L’actuelle durée de vie restante de cette particule est affichée à l’aide de la fonction print (imprimer), à sa droite, pour la démonstration de cet exemple. Dans le cas de TIC-80 print(), est utilisé pour placer du texte sur l’écran graphique, et trace() sur la console texte.

La partie de arguments de la définition que nous utilisons de print sont sous cette forme :

print(texte, début X, début Y, couleur, fonte de largeur fixe, échelle, petite font)

Nous plaçons donc comme texte le temps de vie restant de la particule actuelle (o.lt), placé au centre de sa position son centre (o.x, o.y), déplacé dhorizontalement de +2 pixel (donc à droite) et verticalement -2 pixel (donc au dessus). Nous utilisons une largeur de fonte fixe (true), à l’échelle 1, et une fonte compacte (true):

print(o.lt, o.x+2, o.y-2, 2, true, 1, true)

En Lua on peut définir plusieurs variables sur une même ligne, notamment en croisant leur noms et leurs assignation. Par exemple, ici x=5 y=4 peut également être écrit x,y=5,4.

m=math rnd=m.random
t=0
objs={}
for i=1,rnd(5,8) do
 objs[i]={lt=rnd(10,30),x=120+rnd(-15,15),y=70+rnd(-15,15)}
end
function TIC()
 cls(12)
 for i=#objs,1,-1 do local o=objs[i]
  if o.lt<=0 then table.remove(objs,i)
  else o.lt,o.x,o.y=o.lt-1,o.x+rnd(-1,1),o.y+rnd(-1,1)
   circ(o.x,o.y,1,0)
   print(o.lt,o.x+2,o.y-2,2,true,1,true)
  end
 end
 t=t+1
end

Dans l’exemple à charger la génération est placée dans une fonction appelée generate(). Celle-ci estg appelée lorsque le contenu de la table est nul (#objs==0) afin de créer une boucle simple de régénération.

function generate()
 for i=1,rnd(5,8) do
  objs[i]={lt=rnd(10,30),x=120+rnd(-15,15),y=70+rnd(-15,15)}
 end
end

function TIC()
 ...
 if #objs==0 then generate()end
 t=t+1
end

Si vous désirez dans l’exemple téléchargeable, voir les particules sans leur nombre, il suffit que vous commentiez la ligne comportant le print. Pour commenter du code en Lua, il suffit simplement de le précéder de deux traits d’union.

   -- print(o.lt,o.x+2,o.y-2,2,true,1,true)