Utilisation d’une interruption sur timer avec le PRUSS

Il y a quelques temps, je cherchais à lancer une fonction toutes les n millisecondes avec une bonne précision.
Le but de tout ceci étant de pouvoir faire un dérivation des données collectées en fonction du temps. Et au lieu de mesurer le temps entre chaque donnée, je fixe l’intervalle de temps pour simplifier les calculs.

Ce que je faisais au début: compter le nombre de cycles de la fonction et ajouter des boucles pour perdre du temps. Ça avait un petit désavantage, car dans ma fonction, avec des conditions, le temps d’exécution pouvait être variable, de pas grand chose, certes, mais 50ns sur un nombre important d’itérations, ça peut faire beaucoup, donc jouer sur la précision.

Dans les bouts de code dans les exemples de texane ici: https://github.com/texane/pru_sdk il y a quelquechose qui y ressemble https://github.com/texane/pru_sdk/tree/master/example/pruss_iep

Un extrait du code:

IEP_WAIT_EVENT:
// TODO: cf enable IEP interrupt source TODO, then use SLP 1
LBCO r2, CONST_IEP, IEP_REG_CMP_STATUS, 4
QBBC IEP_WAIT_EVENT, r2, 0
SBCO 0, CONST_IEP, IEP_REG_CMP_STATUS, 4

Comme on peut le voir, il fait du polling, en allant regarder dans le registre ou en est le compteur.
Il y a une deuxième façon de faire: en attendant l’interruption.

Donc, j’ai trouvé ici: https://github.com/jstampfl/PruIEP_Int dans l’exemple iepx.p.

TB2:
       qbbc TB2,r31.t30     // spin here for interrupt

C’est aussi du polling, mais sur le bit d’interruption.

Dans son code, j’aime moins la façon dont il va paramétrer les registres car les adresses sont codées en dur au lieu d’être mises en constantes, une affaire de goût.

J’ai fait un test rapide en faisant un petit mix des deux.
Ce bout de code va générer une interruption toutes les secondes, incrémenter deux compteur, générer une interruption pour le programme en C tournant sur le Beagle Bone, qui va ensuite aller chercher ces compteurs et les afficher.

Ça donne ceci pour le coté PRUSS:

.setcallreg r2.w0  //  Going to use r30
#include 
#include 


// am335xPruReferenceGuide, table 25
#define PRUSS_PRU_CTRL_REG_WAKEUP_EN 0x08

// am335xPruReferenceGuide, table 190
// offsets from CONST_IEP
#define IEP_REG_GLOBAL_CFG 0x00
#define IEP_REG_GLOBAL_STATUS 0x04
#define IEP_REG_COMPEN 0x08
#define IEP_REG_COUNT 0x0c
#define IEP_REG_CMP_CFG 0x40
#define IEP_REG_CMP_STATUS 0x44
#define IEP_REG_CMP0 0x48


.origin 0
.entrypoint MAIN

MAIN:

LBCO r0, CONST_PRUCFG, 4, 4
CLR r0, r0, 4
SBCO r0, CONST_PRUCFG, 4, 4


//
// prepare PRU shared memory access

MOV r0, 0x000000120
MOV r1, CTPPR_0
ST32 r0, r1

MOV r0, 0x00100000
MOV r1, CTPPR_1
ST32 r0, r1


//
// setup IEP module

//    INITIALIZE IEP INTERRUPTS

       mov r14,0xBEBC200           //For CMP0, compare trigger
       sbco r14,c26,0x48,4
       mov r14,0x3                // enable CMP0, and enable
       sbco r14,c26,0x40,4        // counter reset on event
       mov r14,0x1
       sbco r14,c26,0x44,4        // clear status for CMP0
       lbco r14,c26,0x4,4
       sbco r14,c26,0x4,4         // clear GLOBAL status, overflow
       mov r14,0x111              // enable IEP counter, inc 1
       sbco r14,c26,0,4

//    DONE WITH IEP SETUP

//    SETUP CHANNEL MAP
//            map SysEvent to Channel 0, leave 16 - 23 alone set by Linux
       mov r15,0x400            //set up Channel map
       mov r14,0x09090909       // first map all unused events to
       sbco r14,c0,r15,4        //  Channel 9
       mov r15,0x408
       sbco r14,c0,r15,4
       mov r15,0x40C            // skiping offsets 410 & 414, they
       sbco r14,c0,r15,4        // were set by the C program via prussdrv
       mov r18,0x43C            // end for loop
       mov r15,0x414            // start -4 for loop
TB43:
       add r15,r15,4
       sbco r14,c0,r15,4
       qbgt TB43,r15,r18
       mov r14,0x00090909       // map SysEvt 7 to channel 0
       mov r15,0x404            // now do 404, which has the
       sbco r14,c0,r15,4        // entries for 4,5,6,7

//   Done with Channel Map, Host Interrupt Map now
           // Host Interrupt 0 - 3 were setup by Linux   

       mov r14,0x09090909       // map channels 4,5,6,7 to Host Int 9 
       mov r15,0x804
       sbco r14,c0,r15,4
       mov r14,0x00000909       // map channel 8 & 9 to Host Int 9
       mov r15,0x808
       sbco r14,c0,r15,4

       ldi r15, 0x24             //clear all events
       call ALLEVT


       ldi r15,0x28              // enable all events
       call ALLEVT

CALL RSET

//
// r4 starts with 0, and incremented by 1 every IEP event

MOV r4, 0
MOV r5, 0
JMP DO_STORE


//
// wait for IEP events

IEP_WAIT_EVENT:
qbbc IEP_WAIT_EVENT,r31.t30     // spin here for interrupt
MOV r31.b0, PRU0_ARM_INTERRUPT + 16
CALL RSET
// increment and store uint32_t into shared ram (4 bytes)
ADD r4, r4, 1
ADD r5, r5, 2

DO_STORE:
SBCO r4, CONST_PRUSHAREDRAM, 0, 8

JMP IEP_WAIT_EVENT


//
// never reached

MOV r31.b0, PRU0_ARM_INTERRUPT + 16
HALT


RSET:  // Routine to clear & enable system events, also host interrupts
       mov r24,r2           // Save return address
                            // so can call ALLEVT
       lbco r14,c26,0x4,4   // clear GLOBAL_STATUS
       sbco r14,c26,0x4,4
       lbco r14,c26,0x44,4  // clear CMP_STATUS
       sbco r14,c26,0x44,4
       lbco r14,c26,0x44,4
       mov r15,0x24         //  to clear system event
       call ALLEVT
       mov r15,0x28         //  to enable system event
       call ALLEVT

       mov r2,r24            // restore return address
       ret
       
ALLEVT:  
       ldi r14,0x7
       sbco r14, c0 ,r15,4
       ret

et coté BBB:

#include 
#include 
#include 
#include 
#include 
#include "prussdrv.h"
#include "pruss_intc_mapping.h"
#include "../common/mio.h"


/* host pru shared memory */

static int read_words(uint32_t x[2])
{
  static const size_t sharedram_offset = 2048;
  volatile uint32_t* p;

  prussdrv_map_prumem(4, (void**)&p);

  x[0] = p[sharedram_offset + 0];
  x[1] = p[sharedram_offset + 1];

  return 0;
}


/* sigint handler */

static volatile unsigned int is_sigint = 0;

static void on_sigint(int x)
{
  is_sigint = 1;
}


/* main */

int main(int ac, char** av)
{
  tpruss_intc_initdata pruss_intc_initdata = PRUSS_INTC_INITDATA;
  uint32_t x[2];

  prussdrv_init();

  if (prussdrv_open(PRU_EVTOUT_0))
  {
    printf("prussdrv_open open failed\n");
    return -1;
  }

  prussdrv_pruintc_init(&pruss_intc_initdata);

  /* execute code on pru0 */
#define PRU_NUM 0
  prussdrv_exec_program(PRU_NUM, "./iep.bin");
prussdrv_pru_clear_event (PRU_EVTOUT_0, PRU0_ARM_INTERRUPT);
  signal(SIGINT, on_sigint);
  while (is_sigint == 0)
  {
     prussdrv_pru_wait_event (PRU_EVTOUT_0);
    printf("\tINFO: PRU completed transfer.\r\n");
    prussdrv_pru_clear_event (PRU_EVTOUT_0, PRU0_ARM_INTERRUPT);
    read_words(x);
    printf("0x%08x, 0x%08x\n", x[0], x[1]);
  }

  /* disable pru and close memory mapping */
  prussdrv_pru_disable(PRU_NUM);
  prussdrv_exit();

  return 0;
}

C’est fait en vitesse, donc pas très beau, mais c’est fonctionnel.
J’essayerais de formater le code un peu plus tard.

This article was written by Cédric

Menu