Prochaine étape dans votre apprentissage: aller voir comment le buffer circulaire de hardwareSerial est géré avec des pointeurs en lecture et écriture
Un jour j'ai eu besoin d'écrire un programme qui insère un delai dans un flux sonore (regarder le match à la télé avec le son venant de la radio, il y avait un décalage de plusieurs secondes).
J'ai à cette occasion écrit une classe CircularBuffer qui fait le job. Il y a un buffet de SAMPLEs (des int16, int32 ou float), un index d'écriture et un index de (re)lecture qui court derrière le précédent.
Les écritures/lectures se font par paquets de FRAMES_PER_BUFFER* NB_CHAN éléments.
typedef int16_t SAMPLE;
#define NB_CHAN 2 // stereo
#define SAMPLE_RATE (44100) // 44.1 kHz
#define FRAMES_PER_BUFFER (512)
float GdelayTime = 5.f; // 5 sec de retard par defaut
long GDelaiFrames = -1;
//===========================================================
class CircularBuffer {
public:
CircularBuffer ( unsigned long maxframes );
~CircularBuffer ();
float GetMaxDelay () const { return mMaxDelay; }
float SetDelay ( float delay );
void Put ( const SAMPLE* audio, unsigned long frames );
void Get ( SAMPLE* audio, unsigned long frames );
void Clear ( unsigned long frames );
void AdjustDelay ( long frames );
private:
unsigned long mMaxSamples;
float mMaxDelay;
SAMPLE* mAudio;
SAMPLE* mPutPtr;
SAMPLE* mGetPtr;
SAMPLE* mAfterAudio;
};
//------------------------------------------------------------
CircularBuffer::CircularBuffer ( unsigned long maxframes )
{
mMaxSamples = NB_CHAN * maxframes; // en SAMPLEs
mAudio = new SAMPLE [ mMaxSamples ];
memset ( mAudio, 0, mMaxSamples * sizeof(SAMPLE) );
mPutPtr = mGetPtr = mAudio;
mAfterAudio = mAudio + mMaxSamples;
mMaxDelay = ( maxframes - FRAMES_PER_BUFFER ) / (float)SAMPLE_RATE;
printf ( "CircularBuffer %.2f sec. buffer\n", maxframes / (float)SAMPLE_RATE );
printf ( " max delay = %.2f s\n", mMaxDelay );
printf ( "CircularBuffer %ld (0x%lX) samples\n", mMaxSamples, mMaxSamples );
printf ( " mAudio = %p\n", mAudio );
printf ( "mAfterAudio = %p\n", mAfterAudio );
}
//------------------------------------------------------------
CircularBuffer::~CircularBuffer ()
{
delete[] mAudio;
}
//------------------------------------------------------------
float CircularBuffer::SetDelay ( float delay )
{
if ( delay < 0.f ) delay = 0.f;
else if ( delay > mMaxDelay ) delay = mMaxDelay;
printf ( "Set Delay %f s\n", delay );
long frames = delay * SAMPLE_RATE;
if ( frames >= mMaxSamples / NB_CHAN )
frames = mMaxSamples / NB_CHAN;
GDelaiFrames = frames;
GdelayTime = delay;
return delay;
}
// -----------------------------------------------------------
void CircularBuffer::Put ( const SAMPLE* audio, unsigned long frames )
{
long samples_demandes = NB_CHAN * frames;
long samples_reste = ( mAfterAudio - mPutPtr ); // samples
//printf ( "Put %ld samples demandes, reste %ld\n", samples_demandes, samples_reste );
if ( samples_reste < samples_demandes) {
//printf ( " memcpy audio-> %p %lX octets\n", mPutPtr, samples_reste * sizeof(SAMPLE) );
memcpy ( mPutPtr, audio, samples_reste * sizeof(SAMPLE) );
mPutPtr = mAudio;
//printf ( " PutPtr reinit %p\n", mPutPtr );
samples_demandes -= samples_reste;
audio += samples_reste;
}
//printf ( " memcpy audio-> %p %lX octets\n", mPutPtr, samples_demandes * sizeof(SAMPLE) );
memcpy ( mPutPtr, audio, samples_demandes * sizeof(SAMPLE) );
mPutPtr += samples_demandes;
//printf ( " PutPtr is now %p\n", mPutPtr );
}
//------------------------------------------------------------
void CircularBuffer::Get ( SAMPLE* audio, unsigned long frames )
{
long samples_demandes = NB_CHAN * frames;
long samples_reste = ( mAfterAudio - mGetPtr ); // samples
//printf ( "Get %ld samples demandes, reste %ld\n", samples_demandes, samples_reste );
if ( samples_reste < samples_demandes) {
//printf ( " memcpy %p ->audio %lX octets\n", mGetPtr, samples_reste * sizeof(SAMPLE) );
memcpy ( audio, mGetPtr, samples_reste * sizeof(SAMPLE) );
mGetPtr = mAudio;
//printf ( " GetPtr reinit %p\n", mGetPtr );
samples_demandes -= samples_reste;
audio += samples_reste;
}
//printf ( " memcpy %p ->audio %lX octets\n", mGetPtr, samples_demandes * sizeof(SAMPLE) );
memcpy ( audio, mGetPtr, samples_demandes * sizeof(SAMPLE) );
mGetPtr += samples_demandes;
//printf ( " GetPtr is now %p\n", mGetPtr );
}
//------------------------------------------------------------
void CircularBuffer::Clear ( unsigned long frames )
{
long samples_demandes = NB_CHAN * frames;
long samples_reste = mAfterAudio - mPutPtr; // samples
if ( samples_reste < samples_demandes) {
memset ( mPutPtr, 0, samples_reste * sizeof(SAMPLE) );
mPutPtr = mAudio;
samples_demandes -= samples_reste;
}
memset ( mPutPtr, 0, samples_demandes * sizeof(SAMPLE) );
mPutPtr += samples_demandes;
}
//------------------------------------------------------------
void CircularBuffer::AdjustDelay ( long frames )
{
long samples = frames * NB_CHAN;
mGetPtr = mPutPtr - samples;
if ( mGetPtr < mAudio )
mGetPtr += mMaxSamples;
}
//------------------------------------------------------------
Procédons par ordre :
1/
Voilà, Je continue sur le modèle de @vileroi et j'ai supprimé le modulo :
byte indiceMaxTableau = 10; // Taille du tableau
byte indiceZeroTableau = 4; // Décalage de l'origine du tableau
byte valeur = 7; // valeur à ajouter à la fin
int tab[] = {4, 8, 3, 1, 5, 9, 7, 2, 6, 0};
void setup() {
Serial.begin(9600);
imprimer();
Serial.print("\n");
Decalage(tab, valeur);
imprimer();
}
void loop() {}
void Decalage(int *t, byte Valeur) {
t[indiceZeroTableau] = Valeur;
indiceZeroTableau++;
}
void imprimer() {
for (byte j = indiceZeroTableau; j < indiceMaxTableau ; j++) {
Serial.print(tab[j]);
}
for (byte j = 0; j < indiceZeroTableau ; j++) {
Serial.print(tab[j]);
}
}
Si vous l’enlevez Vous devez faire le test de dépassement lors de l’ajout !
Bonsoir @J-M-L,
J'ai un tableau de 10 éléments. Dans cet exemple, j'envoie sur le moniteur série du 5ème au 10ème élément puis du premier au quatrième élément.
Ensuite j'affecte la valeur 7 au 5ème élément puis j'incrémente de 1 la variable indiceZeroTableau
.
Enfin j'envoie sur le moniteur série du 6ème au 10ème élément du tableau puis du premier au cinquième élément. Du coup le dernier élément affiché est égal à 7 (ce qui est voulu).
Donc en fait je ne fais que changer la valeur du 5ème élément et c'est l'unique modification du tableau de 10 éléments.
Il n'y a aucun ajout et je n'arrive pas à comprendre comment il pourrait y avoir dépassement ?
Bonne soirée.
le risque est là
si vous ajoutez d'autres éléments, au bout d'un moment indiceZeroTableau n'est plus dans le tableau. (pas dans votre code puisque vous ne faites qu'un seul ajout, mais en général)
Le modulo sert à le ramener à 0 mais on peut faire cela par un test comme dans mon code posté en post 14 (et qui tient compte aussi du premier remplissage du tableau), c'est plus efficace qu'un modulo qui prend du temps (enfin c'est si vous êtes à quelques microsecondes près)
if (prochaineCase >= taille) {
prochaineCase = 0;
tableauPlein = true;
}
faites tourner ce code pour comprendre la logique
Bonsoir @J-M-L
Vous avez raison, je vois ça demain en fin de matinée.
J'étais sur une version à base de pointeurs mais je vais faire une pause pour gérer le dépassement.
Merci beaucoup
Bonne soirée
Bonjour @J-M-L,
J'ai fait une analyse de votre code en post 14 :
Loop :
1/ Toutes les 100 millisecondes, on affecte une valeur à un tableau de 10 éléments en commençant par son indice 0 et jusqu’à son 9ème. La valeur affectée est générée par la fonction random.
Une variable d’incrémentation qui gére les indices du tableau permet d’affecter une valeur à ses éléments et se nomme prochaineCase, elle est initialisée à 0 au début du programme. Tout se passe dans la procédure void ajouter(byte valeur)
avec la ligne de code :
tableau[prochaineCase++] = valeur;
Aprés chaque affectation de valeur grâce à prochaineCase
, un test vérifie si on est encore dans les limites du tableau dont la taille a été fixée en début de programme à 10 éléments (il évite donc les dépassements) :
if (prochaineCase >= taille)
Si ce n’est pas le cas, on remet à zéro prochaineCase
et la variable booléenne tableauPlein
prend la valeur true définitivement (elle détermine l’affichage dans le moniteur série avec la fonction imprimer()
).
2/ Dans un premier temps la procédure imprimer()
affiche successivement sur le moniteur série tous les éléments du tableau au fur et à mesure de leur affectation jusqu’au dernier élément affecté :
for (byte i = 0; i < prochaineCase; i++) {
Serial.print(tableau[i]);
Serial.write(' ');
A chaque exécution de la boucle for, c’est tout le tableau qui s’affiche jusqu'à la dernière valeur affectée avec un saut de ligne à chaque exécution, de sorte que l’on voit le tableau se remplir de ses valeurs successives jusqu’à la 10ème. A chaque, ligne toutes les valeurs disponibles sont affichées.
Si le tableau est rempli de ses 10 éléments, le tableau est plein et du coup deux boucles for d’affichage sont exécutées à l’infini :
A/ la première permet d’afficher le tableau en partant du dernier indice affecté jusqu’à la fin du tableau ;
B/ la deuxième permet d’afficher le tableau depuis son premier élément jusqu’au dernier indice affecté (exclu).
Pour bien comprendre ce que ces deux boucles permettent d'afficher, il faut travailler à partir du tableau réel et aboutir au tableau virtuel affiché :
77 89 33 48 50 72 34 18 63 29 - Tableau réel (10 premiers éléments affectés)
10 89 33 48 50 72 34 18 63 29 - Tableau réel affectation indice 0
= 89 33 48 50 72 34 18 63 29 10 - Tableau virtuel
10 45 33 48 50 72 34 18 63 29 Tableau réel affectation indice 1
= 33 48 50 72 34 18 63 29 10 45 - Tableau virtuel
10 45 52 48 50 72 34 18 63 29 Tableau réel affectation indice 2
= 48 50 72 34 18 63 29 10 45 52 - Tableau virtuel
…
Voilà @J-M-L dans votre code, vous ajoutez puis modifiez de manière permanente des éléments dans ce tableau de 10 éléments. Vous devez donc vérifier de ne pas vous trouver en dehors du bloc mémoire que le compilateur vous a alloué.
Je pense avoir bien compris ?
Merci et bonne journée.
tout compris ! vous êtes un pro
Merci @J-M-L
C'est loin d'être le cas, la route est encore longue mais tout encouragement venant de votre part est gratifiant !
Merci
Post #23 :
Voici la suite :
2/ Je continue sur le modèle de @vileroi, j'ai supprimé le modulo et j'utilise les pointeurs :
byte indiceMaxTableau = 10; // Taille du tableau
byte indiceZeroTableau = 4; // Décalage de l'origine du tableau
byte valeur = 7;
byte tab[] = {4, 8, 3, 1, 5, 9, 7, 2, 6, 0};
byte *ptr = nullptr;
void setup() {
Serial.begin(9600);
imprimer();
Serial.print("\n");
Decalage(tab, valeur);
imprimer();
}
void loop() {}
void Decalage(byte *t, byte Valeur) {
*(t + indiceZeroTableau) = Valeur;
indiceZeroTableau++;
}
void imprimer() {
for (ptr = (tab + indiceZeroTableau) ; ptr < (tab + 10) ; ptr++) {
Serial.print(*(ptr));
}
for (ptr = tab; ptr < (tab + indiceZeroTableau) ; ptr++) {
Serial.print(*(ptr));
}
}
Bonne soirée.
hum, c'est triché
vous n'utilisez pas vraiment les pointeurs,
*(t + indiceZeroTableau)
c'est comme écrire
t[indiceZeroTableau]
➜ vous avez toujours un index
utiliser les pointeurs ça voudrait ne plus avoir indiceZeroTableau
mais un pointeur sur la case à remplir
tester le débordement, c'est regarder si ce pointeur est arrivé au bout du tableau et le remettre au début du tableau dans ce cas
C'est bien là mon problème.
Je vais me mettre au travail pour trouver la bonne technique, la bonne solution
Merci et bonne soirée.
SI on veut vraiment utiliser les pointeurs, et ne plus utiliser les indices, c'est passer aux listes dont @terwal parlait dans le post #11.
Le tableau est alors représenté en mémoire par une liste. On a un pointeur qui indique le premier élément, et chaque élément pointe sur le suivant. Pour décaler les valeurs qui sont dans la liste "vers la gauche", c'est faire pointer la liste sur le deuxième élément (on demande au premier élément de nous donner le pointeur sur le suivant et on supprime le premier élément. Pour ajouter un élément en fin de liste, on peut parcourir la liste jusqu'à la fin et rajouter un élément derrière.
Avec un seul pointeur sur l'élément suivant, on ne peut parcourir la liste que vers la "droite". Avec deux pointeurs on peut parcourir la liste dans les deux sens. Mais ici on n'a besoin que d'un sens.
Toutefois, dans ton cas définir la liste par deux pointeurs, un sur le premier élément et un sur le dernier, permet facilement d'ajouter un élément en fin. Pas besoin de liste doublement chaînée, juste deux pointeurs sur le début et la fin, et pour chaque élément de la liste, un pointeur sur le suivant.
Mais si c'est pour un tableau d'entier de taille fixe, c'est un outil bien lourd. Mais si c'est pour un problème d'apprentissage des pointeurs, cela se défend.
On peut aussi faire sans liste chaînée, un tableau, un pointeur sur le début, un pointeur sur la fin, un pointeur sur la case à remplir. Comme c’est un tableau, le passage au suivant se fait en incrémentant le pointeur et avec le test qui va bien.
int ptrDebut[10];
size_t taille = sizeof ptrDebut / sizeof * ptrDebut;
int * ptrFin = ptrDebut + taille;
int * ptrCaseVide = ptrDebut;
…
appeler Decalage
une fonction qui ne fait aucun décalage, c'est de la provoc ?
bin oui, la fonction de @A1one fait un décalage !
mais pas la tienne.
Elle décale l’index ou le pointeur
Bon maintenant plus d'infos jusqu'à ce que je trouve.
Je veux trouver tout seul.
ça va mettre un peu de temps car j'ai quelques activités annexes (marche pour la lutte contre le cancer de sein...)
Merci de respecter.
Boonne journée à tous.