Autres articles / Other articles

Définition et manipulation de registres

publication: 2 avril 2022 / mis à jour 19 juillet 2022

Read this page in english

 

Appel à collaboration

Vous développez des montages, simples ou complexes avec ESP32 et ESP32forth.

Partagez-les ici sur ce site.

ESP32forth ne pourra se développer qu'avec la collaboration active de toutes les bonnes volontés.

Vos montages peuvent aider d'autres développeurs.

Les montages des autres développeurs peuvent vous aider.

Pour proposer un article ou un montage, cliquez ici



Les registres ESP32

Extrait de la page 631 de ESP32 Technical Reference Manual

Dans ce document, on retrouve une très grande quantité de registres. Ce sont ces registres qui permettent de contrôler tous les périphériques et ports GPIO de la carte ESP32.

La manipulation de ces registres est effectuée par la couche applicative proposée par ESP32forth. Il n'est donc pas nécessaire d'y accéder directement.

Mais comme on l'a vu dans cet article Acces direct aux registres GPIO, il peut être intéressant de gérer certains registres directement:

Définition de registres

Définir un registre est très simple:

$3FF48898 constant SENS_SAR_DAC_CTRL1_REG   \ DAC control 

Le premier inconvénient, en définissant un registre comme constante, c'est qu'à la lecture du code source, on ne pourra pas distinguer le registre des autres constantes. On va donc définir un mot de création de registres comme ceci:

\ define a register, similar as constant 
: defREG: 
    create ( addr1 --  ) 
        , 
    does>  ( -- regAddr ) 
        @ 
  ; 
 
$3FF48898 defREG: SENS_SAR_DAC_CTRL1_REG   \ DAC control 

De cette manière, en relisant notre code, nous savons que le mot créé est un registre. L'autre intérêt est qu'on peut modifier defREG: pour en changer le comportement: rajout de tests de contrôles, initialisation de paramètres, etc...

Accès au contenu des registres

Valeurs des bits dans le registre SENS_SAR_DAC_CTRL1_REG:

Ce registre contient des bits ou blocs de bits ayant des fonctions définies.

Nous allons d'abord créer un mot permettant de visualiser le contenu d'un registre:

\ display reg content 
: .reg ( reg -- ) 
    base @ >r 
    binary 
    @ <# 
    4 for 
        aft 
            8 for 
                aft  #  then 
            next 
            bl hold 
        then 
    next 
    #> 
    cr space ." 33222222 22221111 11111100 00000000" 
    cr space ." 10987654 32109876 54321098 76543210" 
    cr type 
    r> base ! 
  ; 

Voyons ce que donne le contenu de notre registre SENS_SAR_DAC_CTRL1_REG:

SENS_SAR_DAC_CTRL1_REG .reg
\ display:
 33222222 22221111 11111100 00000000
 10987654 32109876 54321098 76543210
 00000000 00000000 00000000 00000000 ok

Les deux premières lignes permettent de lire verticalement le rang d'un bit dans ce registre, ici, en rouge, 25, dont le contenu est 0.

Pour lire ce bit, on procède comme suit:

SENS_SAR_DAC_CTRL1_REG @ 
1 25 lshift and 

Pour modifier ce bit et le mettre à 1:

1 25 lshift 
SENS_SAR_DAC_CTRL1_REG @ 
or 
SENS_SAR_DAC_CTRL1_REG ! 

Vérifions avec .reg:

SENS_SAR_DAC_CTRL1_REG .reg
\ display:
 33222222 22221111 11111100 00000000
 10987654 32109876 54321098 76543210
 00000010 00000000 00000000 00000000 ok

Si c'est pour une fois, ça dépanne. Voyons comment faire de manière plus efficace...

Manipulation des bits des registres

Reprenons la modification du bit 25 de notre registre SENS_SAR_DAC_CTRL1_REG, voici comment mettre à 1 le bit b25:

SENS_SAR_DAC_CTRL1_REG .reg     \ display: 
\  33222222 22221111 11111100 00000000 
\  10987654 32109876 54321098 76543210 
\  00000000 10000000 00000000 00000000 ok 
 
registers 
1 25 $02000000 SENS_SAR_DAC_CTRL1_REG m! 
SENS_SAR_DAC_CTRL1_REG .reg     \ display: 
\  33222222 22221111 11111100 00000000 
\  10987654 32109876 54321098 76543210 
\  00000010 00000000 00000000 00000000 ok 

On utilise le mot m! ( val shift mask addr -- ) qui accepte quatre paramètres:

Définition de masques

Un masque sert à indiquer quels sont les bits modifiables. Dans le précédent exemple, nous avons modifié le bit b25. Dans la documentation Espressif, le bit b25 est marqué avec le label SENS_DAC_CLK_INV. La solution la plus simple consisterait à créer une constante comme ceci:

1 25 lshift constant SENS_DAC_CLK_INV 

Mais ça ne règle pas le décalage de valeur qui doit être le même que la valeur du masque binaire.

Voyons une manière plus élégante de définir les masques:

: defMASK: 
    create ( mask0 position -- ) 
        dup , 
        lshift , 
    does> ( -- position mask1 ) 
        dup @ 
        swap cell + @ 
  ; 
 
1 25 defMASK: mSENS_DAC_CLK_INV 

Au passage, notez que le nom du masque est préfixé avec la lettre 'm' (pour mask). Ce n'est nullement obligatoire. Mais quand vous aurez compilé de nombreux registres et masques, le préfixe 'm' permettra de vous y retrouver entre registres et masques:

--> words
mSENS_SW_FSTEP mSENS_SW_TONE_EN mSENS_DAC_DIG_FORCE mSENS_DAC_CLK_FORCE_LOW
mSENS_DAC_CLK_FORCE_HIGH mSENS_DAC_CLK_INV defMask: defBit: SENS_SAR_DAC_CTRL2_REG
SENS_SAR_DAC_CTRL1_REG GPIO_ENABLE_W1TC_REG GPIO_ENABLE_W1TS_REG GPIO_ENABLE_REG
GPIO_OUT_W1TC_REG GPIO_OUT_W1TS_REG GPIO_OUT_REG DR_REG_GPIO_BASE PIN_DAC2
PIN_DAC1 CONFIG_IDF_TARGET_ESP32S3 CONFIG_IDF_TARGET_ESP32S2 .reg AdcREG:
mtst mset mclr --DAdirect SENS_DAC_CLK_INV defMASK: input$ c+$! mid$ left$
right$ 0$! $! maxlen$ string $= FORTH camera-server camera telnetd bterm
......

Le mot défini avec defMASK: dépose sur la pile le décalage du masque, ici 25 pour mSENS_DAC_CLK_INV et la valeur du masque binaire à appliquer.

Reprenons la modification du bit b25 avec cette définition de masque:

1 mSENS_DAC_CLK_INV SENS_SAR_DAC_CTRL1_REG m! 
SENS_SAR_DAC_CTRL1_REG .reg   \ display: 
\  33222222 22221111 11111100 00000000 
\  10987654 32109876 54321098 76543210 
\  00000010 00000000 00000000 00000000    
 
0 mSENS_DAC_CLK_INV SENS_SAR_DAC_CTRL1_REG m! 
SENS_SAR_DAC_CTRL1_REG .reg   \ display: 
\  33222222 22221111 11111100 00000000 
\  10987654 32109876 54321098 76543210 
\  00000000 00000000 00000000 00000000 

Passer du langage C au langage FORTH

Avec ESP32Forth, il y a deux solutions pour rajouter des primitives au dictionnaire FORTH:

  1. réécrire le code source de ESP32Forth en rajoutant les primitives souhaitées;
  2. réécrire ces mots du langage C en FORTH

La première solution, en langage C est écrite ici:

#ifndef ENABLE_DAC_SUPPORT
# define OPTIONAL_DAC_SUPPORT
# else
# include <driver/dac.h>
# include <driver/dac_common.h>
# include <soc/rtc_io_reg.h>
# include <soc/rtc_cntl_reg.h>
# include <soc/sens_reg.h>
# include <soc/rtc.h>
# define OPTIONAL_DAC_SUPPORT \
Y(dac_output_enable, n0 = dac_output_enable( (dac_channel_t) n0 ) ) \
Y(dac_output_disable, n0 = dac_output_disable( (dac_channel_t) n0 ) ) \
Y(dac_output_voltage, n0 = dac_output_voltage((dac_channel_t ) n1, (gpio_num_t ) n0); NIP ) \
Y(dac_cw_generator_enable, PUSH dac_cw_generator_enable () ) \
Y(dac_cw_generator_disable, PUSH dac_cw_generator_disable () ) \
Y(dac_i2s_enable, PUSH dac_i2s_enable() ) \
Y(dac_i2s_disable, PUSH dac_i2s_disable() ) \
Y(rtc_freq_div_set, REG_SET_FIELD(RTC_CNTL_CLK_CONF_REG, RTC_CNTL_CK8M_DIV_SEL, n0 ); DROP ) \
Y(dac_freq_step_set, SET_PERI_REG_BITS(SENS_SAR_DAC_CTRL1_REG, SENS_SW_FSTEP, n0, SENS_SW_FSTEP_S); DROP ) \
Y(dac1_scale_set, SET_PERI_REG_BITS(SENS_SAR_DAC_CTRL2_REG, SENS_DAC_SCALE1, 0, SENS_DAC_SCALE1_S); ) \
Y(dac2_scale_set, SET_PERI_REG_BITS(SENS_SAR_DAC_CTRL2_REG, SENS_DAC_SCALE2, 0, SENS_DAC_SCALE2_S); ) \
Y(dac1_offset_set, SET_PERI_REG_BITS(SENS_SAR_DAC_CTRL2_REG, SENS_DAC_DC1, n0, SENS_DAC_DC1_S); DROP ) \
Y(dac2_offset_set, SET_PERI_REG_BITS(SENS_SAR_DAC_CTRL2_REG, SENS_DAC_DC2, n0, SENS_DAC_DC2_S); DROP ) \
Y(dac1_invert_set, SET_PERI_REG_BITS(SENS_SAR_DAC_CTRL2_REG, SENS_DAC_INV1, n0, SENS_DAC_INV1_S); DROP ) \
Y(dac2_invert_set, SET_PERI_REG_BITS(SENS_SAR_DAC_CTRL2_REG, SENS_DAC_INV2, n0, SENS_DAC_INV2_S); DROP ) \
Y(dac1_cosine_enable, SET_PERI_REG_MASK(SENS_SAR_DAC_CTRL2_REG, SENS_DAC_CW_EN1_M); ) \
Y(dac2_cosine_enable, SET_PERI_REG_MASK(SENS_SAR_DAC_CTRL2_REG, SENS_DAC_CW_EN2_M); ) \
Y(dacWrite, dacWrite(n1, n0); DROPn(2))
#endif

La seconde solution consiste à regarder le code source d'un fichier écrit en langage C et essayons de comprendre comment les registres sont manipulas dans ce langage.

Extrait du fichier dac-cosine.c:

/*
 * Enable cosine waveform generator on a DAC channel
 */
void dac_cosine_enable(dac_channel_t channel)
{
    // Enable tone generator common to both channels
    SET_PERI_REG_MASK(SENS_SAR_DAC_CTRL1_REG, SENS_SW_TONE_EN);
    switch(channel) {
        case DAC_CHANNEL_1:
            // Enable / connect tone tone generator on / to this channel
            SET_PERI_REG_MASK(SENS_SAR_DAC_CTRL2_REG, SENS_DAC_CW_EN1_M);
            // Invert MSB, otherwise part of waveform will have inverted
            SET_PERI_REG_BITS(SENS_SAR_DAC_CTRL2_REG, SENS_DAC_INV1, 2, SENS_DAC_INV1_S);
            break;
        case DAC_CHANNEL_2:
            SET_PERI_REG_MASK(SENS_SAR_DAC_CTRL2_REG, SENS_DAC_CW_EN2_M);
            SET_PERI_REG_BITS(SENS_SAR_DAC_CTRL2_REG, SENS_DAC_INV2, 2, SENS_DAC_INV2_S);
            break;
        default :
           printf("Channel %d\n", channel);
    }
}

Une des fonctions C qui revient souvent est SET_PERI_REG_MASK. Cette fonction met à 1 les bits désignés par un masque dans un registre. Exemple:

            SET_PERI_REG_MASK(SENS_SAR_DAC_CTRL2_REG, SENS_DAC_CW_EN1_M);

La fonction C qui met à 0 les bits désignés par un masque dans un registre est CLEAR_PERI_REG_MASK.

On va s'intéresser à la manière dont on va réécrire dac_cosine_enable(dac_channel_t channel) en langage FORTH.

On voit déjà que le registre SENS_SAR_DAC_CTRL2_REG est mentionné. On va donc définir ce registre:
$3FF4889c defREG: SENS_SAR_DAC_CTRL2_REG   \ DAC output control 

Dans ce registre SENS_SAR_DAC_CTRL2_REG, les deux bits qui nous intéressent sont b24 et b25. Définissons les masques correspondants:

1 24 defMASK: mSENS_DAC_CW_EN1   \ selects CW generator as source for PDAC1 
1 25 defMASK: mSENS_DAC_CW_EN2   \ selects CW generator as source for PDAC2 

Re-writing in progress / Ré-écriture en cours

Conclusion

La programmation 'bare-metal' agissant directement sur les registres de ESP32 ne nécessite pas de définir en FORTH tous les registres et masques de registres comme le fait le langage C. Limitez-vous aux registres et masques essentiels pour votre application.

Il est conseillé d'utiliser les noms des registres et masques de registres tels que figurant dans la documentation Espressif, ou à défaut les noms de registres utilisés dans les codes sources en langage C.

La gestion de certains périphériques est très complexe. La documentation Espresif est avare d'exemples sur l'utilisation directe des registres.


Legal: site web personnel sans commerce / personal site without seling