Générateur ECG/Signal Carré ou sinusoïdal

Bonjour,

Dans le cadre de mon travail, je contrôle et dépanne régulièrement des moniteurs multiparamétriques (ECG,PNI, SPO2) de salle de bloc. Dans 90% de mes interventions c'est le câble ECG 3 brins qui est défectueux.

Je cherche à réaliser un montage simple et compact afin de simuler un signal ECG (dans le meilleur des mondes) sinon un signal carré/sinusoïdale. Ce montage sera intégré dans un petit boitier adapté et pourra tenir dans la poche. Il me permettra de contrôler l'intégrité du câble ECG 3 brins rapidement.

Je n'ai que de fine base en arduino, auriez vous des pistes que je pourrais explorer ?
Je dispose d'une carte arduino Nano Every.

Merci de vos futures réponses.
Si vous avez besoin de plus d'informations, n'hésitez pas.

TU veux juste faire un testeur de continuité (comprendre on débranche le câble des deux côtés, un boîtier à un bout un autre à l'autre bout et ça bippe si c'est OK) ou tu veux remplacer le patient (enfin les électrodes je suppose) par un montage qui envoie à l'appareil d'ECG un signal électrique à peu près semblable qui te permette de valider l'ensemble de la chaîne ?

Proposition 1 : l'arduino est inutile et un bête montage avec un buzzer peut suffire

Proposition 2 : il faudrait que tu nous donne un peu plus de détails sur les caractéristiques électriques de ton "patient". À vue de nez, je parie sur des tensions très faibles, et j'ai peur que dans ce cas envoyer à l'appareil d'ECG un signal à 3,3 V ne lui fasse pas super plaisir.

Il faudrait que tu connaisses ce qui est censé passé dans ton câble pour commencer.

Si le câble est facilement détachable, un testeur de continuité pourrait comme le dis @ProfesseurMephisto suffire.

Je ne peux pas juger de la pertinence de : https://www.youtube.com/watch?app=desktop&v=mUsFqI2mpko mais apparemment, on a des signaux électriques de quelques dixièmes de millivolts différentiels, autour d'une masse commune.

Une remarque pour @ez3khiel : il n'y a pas de vraie sortie analogique (encore moins différentielle) sur un nano. Une partie d’électronique « traditionnelle » (mais pas forcement très complexe) sera nécessaire et suivant tes compétences en la matière, pourrait même rendre le nano totalement superflu.

Merci de vos réponses.

En effet, il s'agit de la proposition n°2.
Les signaux électriques sont très faibles. L'échelle par défaut sur le moniteur est de 1mV. En revanche le câble et le moniteur peuvent recevoir de plus forte tension en cas d'utilisation d'un défibrillateur par exemple. Ils sont donc équipés de protection.

Il arrive parfois que la continuité soit bonne. Cependant les câbles sont équipés de blindage pour éviter les interférences. Ce dernier peut être défectueux et ainsi créer des artefacts.
C'est pour ça que j'aimerais vraiment privilégier un signal à un test de continuité.

Ca ne me dérange pas d'avoir une partie d'électronique traditionnelle en plus du Nano. J'ai quelques bases en électronique.

J'ai commencé à chercher des projets déjà existant.

J'ai notamment trouver ce projet de Jim Lynch : ECG Simulator. Il semble correspondre à ce que je recherche mais il ne semble pas adapté à l'utilisation avec une carte Nano. (J'aimerais vraiment rentre l'outils ultra compact.)

J'ai également trouvé ce projet : ECG Simulator HTM Workshop. Il utilise une carte nano mais il est plus complexe au niveau du circuit électronique. J'ai également eu accès au fichier .ino mais j'obtiens une incroyable quantité d'erreur lors du téléversement.

Je ne suis pas un spécialiste, mais le blindage est un conducteur comme un autre, tu devrais pouvoir tester sa continuité non ?

Du coup au vu de la remarque précédente, il vaut mieux tester sur des tensions similaires ?

Il semble utiliser un ATMega328P, je suppose que le code doit être compatible avec le ATMega328 qui équipe la Nano.
Mais je suppose que des spécialistes pourrons nous en dire plus.

J'aurais tendance à dire que comme il n'y a que des résistances pour faire des ponts diviseurs de tension, c'est plus simple?

Fait nous un copier/coller texte des erreurs, peut être que ce n'est pas grand chose.

Les projets en question sont des simulateurs (des produits amateur) et tu voudrais les brancher sur un ECG?
J'ai une remarque, je serais très surpris que le fabricant soit d'accord avec ta proposition. La maintenance d'un appareil médical exige de respecter un certain protocole et de travailler avec des appareils homologués. Si tu connectes un appareil non homologué tu engages ta responsabilité et en cas de problème je n'aimerais pas être à ta place.
Après, si c'est uniquement pour vérifier le câble (donc déconnecté de l'appareil médical) il n'est sans doute pas très utile d'avoir des signaux identiques. Il faudrait définir quels paramètres du câble tu veux vérifier et développer ton outil en conséquence.

  • continuité?
  • diaphonie?
  • bande-passante?
1 Like

Je comprends ta remarque. Je précise que ce dispositif ne servira pas à vérifier le bon fonctionnement du moniteur. J’ai déjà des appareils de contrôle assez volumineux pour le faire. Je ne cherche pas à remplacer la maintenance préventive annuel de ce type de dispositif.

Le but c’est d’avoir un outils de diagnostic qui puisse m’indiquer rapidement si le câble ECG est ok.

Je vous transmets les rapports d’erreur dès demain.
En attendant, vous trouverez ci dessous les github des deux projets :

Merci encore de vos réponse.
Bonne soirée.

Bonjour,

Je vous transfert les messages d'erreur des deux projets. J'ai essayé de comprendre d'où venait l'erreur mais je n'ai rien trouvé.

Pour que ça soit plus simple, je devrais me concentrer sur l'un des deux projets. Le projet de Jim Lynch me semble plus simple à mettre en oeuvre.

Message d'erreur - Projet n°1 :

C:\Users\phoullegatte\Desktop\SIM_ECG_1\SIM_ECG_1.ino: In function 'void setup()':
C:\Users\phoullegatte\Desktop\SIM_ECG_1\SIM_ECG_1.ino:183:3: error: 'TIMSK2' was not declared in this scope
   TIMSK2 &=~(1<<TOIE2);
   ^~~~~~
C:\Users\phoullegatte\Desktop\SIM_ECG_1\SIM_ECG_1.ino:183:3: note: suggested alternative: 'TIMERB2'
   TIMSK2 &=~(1<<TOIE2);
   ^~~~~~
   TIMERB2
C:\Users\phoullegatte\Desktop\SIM_ECG_1\SIM_ECG_1.ino:183:17: error: 'TOIE2' was not declared in this scope
   TIMSK2 &=~(1<<TOIE2);
                 ^~~~~
C:\Users\phoullegatte\Desktop\SIM_ECG_1\SIM_ECG_1.ino:186:3: error: 'TCCR2A' was not declared in this scope
   TCCR2A &= ~((1<<WGM21) | (1<<WGM20));
   ^~~~~~
C:\Users\phoullegatte\Desktop\SIM_ECG_1\SIM_ECG_1.ino:186:3: note: suggested alternative: 'TCB2'
   TCCR2A &= ~((1<<WGM21) | (1<<WGM20));
   ^~~~~~
   TCB2
C:\Users\phoullegatte\Desktop\SIM_ECG_1\SIM_ECG_1.ino:186:19: error: 'WGM21' was not declared in this scope
   TCCR2A &= ~((1<<WGM21) | (1<<WGM20));
                   ^~~~~
C:\Users\phoullegatte\Desktop\SIM_ECG_1\SIM_ECG_1.ino:186:32: error: 'WGM20' was not declared in this scope
   TCCR2A &= ~((1<<WGM21) | (1<<WGM20));
                                ^~~~~
C:\Users\phoullegatte\Desktop\SIM_ECG_1\SIM_ECG_1.ino:187:3: error: 'TCCR2B' was not declared in this scope
   TCCR2B &= ~(1<<WGM22);
   ^~~~~~
C:\Users\phoullegatte\Desktop\SIM_ECG_1\SIM_ECG_1.ino:187:3: note: suggested alternative: 'TCB2'
   TCCR2B &= ~(1<<WGM22);
   ^~~~~~
   TCB2
C:\Users\phoullegatte\Desktop\SIM_ECG_1\SIM_ECG_1.ino:187:18: error: 'WGM22' was not declared in this scope
   TCCR2B &= ~(1<<WGM22);
                  ^~~~~
C:\Users\phoullegatte\Desktop\SIM_ECG_1\SIM_ECG_1.ino:190:3: error: 'ASSR' was not declared in this scope
   ASSR &= ~(1<<AS2);
   ^~~~
C:\Users\phoullegatte\Desktop\SIM_ECG_1\SIM_ECG_1.ino:190:3: note: suggested alternative: 'SS'
   ASSR &= ~(1<<AS2);
   ^~~~
   SS
C:\Users\phoullegatte\Desktop\SIM_ECG_1\SIM_ECG_1.ino:190:16: error: 'AS2' was not declared in this scope
   ASSR &= ~(1<<AS2);
                ^~~
C:\Users\phoullegatte\Desktop\SIM_ECG_1\SIM_ECG_1.ino:190:16: note: suggested alternative: 'A2'
   ASSR &= ~(1<<AS2);
                ^~~
                A2
C:\Users\phoullegatte\Desktop\SIM_ECG_1\SIM_ECG_1.ino:193:18: error: 'OCIE2A' was not declared in this scope
   TIMSK2 &= ~(1<<OCIE2A);
                  ^~~~~~
C:\Users\phoullegatte\Desktop\SIM_ECG_1\SIM_ECG_1.ino:196:17: error: 'CS22' was not declared in this scope
   TCCR2B |= (1<<CS22)  | (1<<CS20); // Set bits
                 ^~~~
C:\Users\phoullegatte\Desktop\SIM_ECG_1\SIM_ECG_1.ino:196:30: error: 'CS20' was not declared in this scope
   TCCR2B |= (1<<CS22)  | (1<<CS20); // Set bits
                              ^~~~
C:\Users\phoullegatte\Desktop\SIM_ECG_1\SIM_ECG_1.ino:197:18: error: 'CS21' was not declared in this scope
   TCCR2B &= ~(1<<CS21);             // Clear bit
                  ^~~~
C:\Users\phoullegatte\Desktop\SIM_ECG_1\SIM_ECG_1.ino:210:3: error: 'TCNT2' was not declared in this scope
   TCNT2 = tcnt2;
   ^~~~~
C:\Users\phoullegatte\Desktop\SIM_ECG_1\SIM_ECG_1.ino:210:3: note: suggested alternative: 'TCB2'
   TCNT2 = tcnt2;
   ^~~~~
   TCB2
C:\Users\phoullegatte\Desktop\SIM_ECG_1\SIM_ECG_1.ino: In function 'void TIMER2_OVF_vect()':
C:\Users\phoullegatte\Desktop\SIM_ECG_1\SIM_ECG_1.ino:300:3: error: 'TCNT2' was not declared in this scope
   TCNT2 = tcnt2;
   ^~~~~
C:\Users\phoullegatte\Desktop\SIM_ECG_1\SIM_ECG_1.ino:300:3: note: suggested alternative: 'TCB2'
   TCNT2 = tcnt2;
   ^~~~~
   TCB2

exit status 1

Compilation error: 'TIMSK2' was not declared in this scope

Message d'erreur - Projet n°2 :

C:\Users\phoullegatte\Desktop\SIM_ECG_2\SIM_ECG_2.ino: In function 'uint8_t get_mode()':
C:\Users\phoullegatte\Desktop\SIM_ECG_2\SIM_ECG_2.ino:83:13: error: 'PIND' was not declared in this scope
     return((PIND >> 2) & 0x7);
             ^~~~
C:\Users\phoullegatte\Desktop\SIM_ECG_2\SIM_ECG_2.ino:83:13: note: suggested alternative: 'PIN0'
     return((PIND >> 2) & 0x7);
             ^~~~
             PIN0
C:\Users\phoullegatte\Desktop\SIM_ECG_2\SIM_ECG_2.ino: In function 'void disable_resp()':
C:\Users\phoullegatte\Desktop\SIM_ECG_2\SIM_ECG_2.ino:88:17: error: no match for 'operator|' (operand types are 'DDRBClass' and 'int')
     DDRB = DDRB | 0x4;
            ~~~~~^~~~~
C:\Users\phoullegatte\Desktop\SIM_ECG_2\SIM_ECG_2.ino:89:5: error: 'TCCR2B' was not declared in this scope
     TCCR2B = 0;
     ^~~~~~
C:\Users\phoullegatte\Desktop\SIM_ECG_2\SIM_ECG_2.ino:89:5: note: suggested alternative: 'TCB2'
     TCCR2B = 0;
     ^~~~~~
     TCB2
C:\Users\phoullegatte\Desktop\SIM_ECG_2\SIM_ECG_2.ino: In function 'void enable_resp()':
C:\Users\phoullegatte\Desktop\SIM_ECG_2\SIM_ECG_2.ino:94:5: error: 'TCCR2B' was not declared in this scope
     TCCR2B = 0x7;       // (clk/1024) prescaler
     ^~~~~~
C:\Users\phoullegatte\Desktop\SIM_ECG_2\SIM_ECG_2.ino:94:5: note: suggested alternative: 'TCB2'
     TCCR2B = 0x7;       // (clk/1024) prescaler
     ^~~~~~
     TCB2
C:\Users\phoullegatte\Desktop\SIM_ECG_2\SIM_ECG_2.ino: In function 'void pwm_dc(uint8_t)':
C:\Users\phoullegatte\Desktop\SIM_ECG_2\SIM_ECG_2.ino:102:5: error: 'OCR1A' was not declared in this scope
     OCR1A = duty_cycle;
     ^~~~~
C:\Users\phoullegatte\Desktop\SIM_ECG_2\SIM_ECG_2.ino: In function 'void pwm_array_sequence(const uint8_t*, uint8_t)':
C:\Users\phoullegatte\Desktop\SIM_ECG_2\SIM_ECG_2.ino:110:19: error: no match for 'operator&' (operand types are 'PORTDClass' and 'int')
     PORTD = PORTD & ~0x20;
             ~~~~~~^~~~~~~
C:\Users\phoullegatte\Desktop\SIM_ECG_2\SIM_ECG_2.ino:112:31: error: 'PD5' was not declared in this scope
         PORTD = PORTD | (1 << PD5);
                               ^~~
C:\Users\phoullegatte\Desktop\SIM_ECG_2\SIM_ECG_2.ino:112:31: note: suggested alternative: 'PD'
         PORTD = PORTD | (1 << PD5);
                               ^~~
                               PD
C:\Users\phoullegatte\Desktop\SIM_ECG_2\SIM_ECG_2.ino: In function 'void TIMER2_OVF_vect()':
C:\Users\phoullegatte\Desktop\SIM_ECG_2\SIM_ECG_2.ino:126:21: error: no match for 'operator^' (operand types are 'DDRBClass' and 'int')
         DDRB = DDRB ^ 0x4;
                ~~~~~^~~~~
C:\Users\phoullegatte\Desktop\SIM_ECG_2\SIM_ECG_2.ino:127:23: error: no match for 'operator&' (operand types are 'PORTBClass' and 'int')
         PORTB = PORTB & ~0x4;
                 ~~~~~~^~~~~~
C:\Users\phoullegatte\Desktop\SIM_ECG_2\SIM_ECG_2.ino:129:24: error: no match for 'operator^' (operand types are 'PORTBClass' and 'int')
         PORTB = (PORTB ^ 0x20);
                  ~~~~~~^~~~~~
C:\Users\phoullegatte\Desktop\SIM_ECG_2\SIM_ECG_2.ino: In function 'void setup()':
C:\Users\phoullegatte\Desktop\SIM_ECG_2\SIM_ECG_2.ino:158:5: error: 'TCCR1A' was not declared in this scope
     TCCR1A |= _BV(COM1A1) | _BV(WGM10);
     ^~~~~~
C:\Users\phoullegatte\Desktop\SIM_ECG_2\SIM_ECG_2.ino:158:5: note: suggested alternative: 'TCB1'
     TCCR1A |= _BV(COM1A1) | _BV(WGM10);
     ^~~~~~
     TCB1
In file included from c:\users\phoullegatte\appdata\local\arduino15\packages\arduino\tools\avr-gcc\7.3.0-atmel3.6.1-arduino5\avr\include\avr\io.h:99:0,
                 from c:\users\phoullegatte\appdata\local\arduino15\packages\arduino\tools\avr-gcc\7.3.0-atmel3.6.1-arduino5\avr\include\avr\pgmspace.h:90,
                 from C:\Users\phoullegatte\AppData\Local\Arduino15\packages\arduino\hardware\megaavr\1.8.8\cores\arduino/api/String.h:31,
                 from C:\Users\phoullegatte\AppData\Local\Arduino15\packages\arduino\hardware\megaavr\1.8.8\cores\arduino/api/IPAddress.h:24,
                 from C:\Users\phoullegatte\AppData\Local\Arduino15\packages\arduino\hardware\megaavr\1.8.8\cores\arduino/api/ArduinoAPI.h:30,
                 from C:\Users\phoullegatte\AppData\Local\Arduino15\packages\arduino\hardware\megaavr\1.8.8\cores\arduino/Arduino.h:23,
                 from C:\Users\phoullegatte\AppData\Local\Temp\arduino\sketches\05CD0A13703A8F3DC2A88C3960F9E81D\sketch\SIM_ECG_2.ino.cpp:1:
C:\Users\phoullegatte\Desktop\SIM_ECG_2\SIM_ECG_2.ino:158:19: error: 'COM1A1' was not declared in this scope
     TCCR1A |= _BV(COM1A1) | _BV(WGM10);
                   ^
C:\Users\phoullegatte\Desktop\SIM_ECG_2\SIM_ECG_2.ino:158:33: error: 'WGM10' was not declared in this scope
     TCCR1A |= _BV(COM1A1) | _BV(WGM10);
                                 ^
C:\Users\phoullegatte\Desktop\SIM_ECG_2\SIM_ECG_2.ino:158:33: note: suggested alternative: 'BPM120'
C:\Users\phoullegatte\Desktop\SIM_ECG_2\SIM_ECG_2.ino:159:5: error: 'TCCR1B' was not declared in this scope
     TCCR1B |= _BV(CS10) | _BV(WGM12);
     ^~~~~~
C:\Users\phoullegatte\Desktop\SIM_ECG_2\SIM_ECG_2.ino:159:5: note: suggested alternative: 'TCB1'
     TCCR1B |= _BV(CS10) | _BV(WGM12);
     ^~~~~~
     TCB1
In file included from c:\users\phoullegatte\appdata\local\arduino15\packages\arduino\tools\avr-gcc\7.3.0-atmel3.6.1-arduino5\avr\include\avr\io.h:99:0,
                 from c:\users\phoullegatte\appdata\local\arduino15\packages\arduino\tools\avr-gcc\7.3.0-atmel3.6.1-arduino5\avr\include\avr\pgmspace.h:90,
                 from C:\Users\phoullegatte\AppData\Local\Arduino15\packages\arduino\hardware\megaavr\1.8.8\cores\arduino/api/String.h:31,
                 from C:\Users\phoullegatte\AppData\Local\Arduino15\packages\arduino\hardware\megaavr\1.8.8\cores\arduino/api/IPAddress.h:24,
                 from C:\Users\phoullegatte\AppData\Local\Arduino15\packages\arduino\hardware\megaavr\1.8.8\cores\arduino/api/ArduinoAPI.h:30,
                 from C:\Users\phoullegatte\AppData\Local\Arduino15\packages\arduino\hardware\megaavr\1.8.8\cores\arduino/Arduino.h:23,
                 from C:\Users\phoullegatte\AppData\Local\Temp\arduino\sketches\05CD0A13703A8F3DC2A88C3960F9E81D\sketch\SIM_ECG_2.ino.cpp:1:
C:\Users\phoullegatte\Desktop\SIM_ECG_2\SIM_ECG_2.ino:159:19: error: 'CS10' was not declared in this scope
     TCCR1B |= _BV(CS10) | _BV(WGM12);
                   ^
C:\Users\phoullegatte\Desktop\SIM_ECG_2\SIM_ECG_2.ino:159:19: note: suggested alternative: 'B110'
C:\Users\phoullegatte\Desktop\SIM_ECG_2\SIM_ECG_2.ino:159:31: error: 'WGM12' was not declared in this scope
     TCCR1B |= _BV(CS10) | _BV(WGM12);
                               ^
C:\Users\phoullegatte\Desktop\SIM_ECG_2\SIM_ECG_2.ino:159:31: note: suggested alternative: 'BPM120'
C:\Users\phoullegatte\Desktop\SIM_ECG_2\SIM_ECG_2.ino:165:5: error: 'TCCR2A' was not declared in this scope
     TCCR2A = 0;
     ^~~~~~
C:\Users\phoullegatte\Desktop\SIM_ECG_2\SIM_ECG_2.ino:165:5: note: suggested alternative: 'TCB2'
     TCCR2A = 0;
     ^~~~~~
     TCB2
C:\Users\phoullegatte\Desktop\SIM_ECG_2\SIM_ECG_2.ino:167:5: error: 'TCNT2' was not declared in this scope
     TCNT2 = 0;
     ^~~~~
C:\Users\phoullegatte\Desktop\SIM_ECG_2\SIM_ECG_2.ino:167:5: note: suggested alternative: 'TCB2'
     TCNT2 = 0;
     ^~~~~
     TCB2
C:\Users\phoullegatte\Desktop\SIM_ECG_2\SIM_ECG_2.ino:168:5: error: 'TIMSK2' was not declared in this scope
     TIMSK2 = _BV(TOIE2);    // enable TIMER2 overflow interrupt
     ^~~~~~
C:\Users\phoullegatte\Desktop\SIM_ECG_2\SIM_ECG_2.ino:168:5: note: suggested alternative: 'TIMERB2'
     TIMSK2 = _BV(TOIE2);    // enable TIMER2 overflow interrupt
     ^~~~~~
     TIMERB2
In file included from c:\users\phoullegatte\appdata\local\arduino15\packages\arduino\tools\avr-gcc\7.3.0-atmel3.6.1-arduino5\avr\include\avr\io.h:99:0,
                 from c:\users\phoullegatte\appdata\local\arduino15\packages\arduino\tools\avr-gcc\7.3.0-atmel3.6.1-arduino5\avr\include\avr\pgmspace.h:90,
                 from C:\Users\phoullegatte\AppData\Local\Arduino15\packages\arduino\hardware\megaavr\1.8.8\cores\arduino/api/String.h:31,
                 from C:\Users\phoullegatte\AppData\Local\Arduino15\packages\arduino\hardware\megaavr\1.8.8\cores\arduino/api/IPAddress.h:24,
                 from C:\Users\phoullegatte\AppData\Local\Arduino15\packages\arduino\hardware\megaavr\1.8.8\cores\arduino/api/ArduinoAPI.h:30,
                 from C:\Users\phoullegatte\AppData\Local\Arduino15\packages\arduino\hardware\megaavr\1.8.8\cores\arduino/Arduino.h:23,
                 from C:\Users\phoullegatte\AppData\Local\Temp\arduino\sketches\05CD0A13703A8F3DC2A88C3960F9E81D\sketch\SIM_ECG_2.ino.cpp:1:
C:\Users\phoullegatte\Desktop\SIM_ECG_2\SIM_ECG_2.ino:168:18: error: 'TOIE2' was not declared in this scope
     TIMSK2 = _BV(TOIE2);    // enable TIMER2 overflow interrupt
                  ^

exit status 1

Compilation error: 'PIND' was not declared in this scope

Tu as sélectionné quelle référence de carte pour compiler tes codes?

J'ai utilisé une carte Arduino Nano Every.
J'ai également une carte Arduino Nano 33 BLE et une carte Arduino Nano 33 IoT. Les messages d'erreur sont identiques sur ces deux cartes.

Les projets n'utilisaient pas une Arduino Nano, donc a base de ATmega328?

Les 2 projets cités utilisent une carte à base d'ATmega328 et dans les 2 codes il y a des accès directs aux registres du microcontrôleur.
Or les 2 cartes que tu veux utiliser sont basées sur d'autres microcontrôleurs d'où les erreurs que tu rencontres.
Il faudrait donc soit

  • changer de carte pour revenir à une Arduino Nano
  • adapter le code pour la nouvelle carte que tu voudrais utiliser au final. Là, il va falloir mettre les mains dans le cambouis pour trouver les équivalences entre des architectures différentes.

Je me disais justement que cela pouvait venir de là.
Je ne vais pas m’embêter je vais commander une carte Arduino Nano avec le bon microcontrôleur.

Je vous tiens au courant. Merci.

Bonjour,

C'était bien ça. Il n'y a plus d'erreurs lors de la compilation et du téléversement. :wink:

En revanche, je n'ai pas d'écran 7 segments dans mon stock de pièce et celui que utilisé n'est plus vendu. Mais j'ai un écran Grove-LCD RGB qui ferait l'affaire à condition de faire des modifications.
Pensez-vous qu'il est possible de modifier simplement le code et le montage ?

Pour rappel, voici le code que j'utilise :

// ***************************************************************************************************
//                                    ECG SIMULATOR
//
//  Purpose: simulate the normal sinus rhythm ECG signal (3-leads RA,LA,RL)
//
//  Background:
// 
//  In normal electrocardiography (ECG or EKG if you're German), three leads make up the
//  Einthoven's triangle. Two leads are taped to the right and left side of the chest above
//  the heart (RA = right arm, LA = left arm) and one lead is taped to the lower chest, typically
//  on the right hip (RL = right leg). 
//                                           
//  It's important to know that these ECG signals are millivolts in amplitude. This can be achieved by          
//  feeding the D/A converter through a voltage divider to get to the millivolt levels.
//
//  
//  The ECG signal:
//
//  I found a suitable ECG waveform from the internet. Here is how I converted a picture from my
//  monitor screen to a C language array of A/D values, each spaced 1.00 msec apart.
//                                           
//       A. Screen shot of waveform using the free screen capture program MWSNAP          
//                http://www.mirekw.com/winfreeware/mwsnap.html 
//
//       B. Digitize the jpeg waveform using the free digitizing program ENGAUGE
//                http://digitizer.sourceforge.net/
//
//            
//       C: I wrote a Python program to convert the rather irregular samples from ENGAUGE
//          to an array of values spaced 1.0 milliseconds apart using linear interpolation.                                 
//          Then I created a text file where these data points were part of a C language array
//          construct; that is, the data points are C initializers. 
//
//       D: Cut-and-paste from the text file the C data array with initializers into the 
//          Arduino sketch below.
//  
//
//  Arduino Resources:   
//
//    Digital Output # 9  - chip select the 7-segment display SPI port (low to select)
//    Digital Output # 10 - chip select for D/A converter  (low to select)
//    Digital Output # 11 - SDI data to the D/A converter  (SPI interface)
//    Digital Output # 13 - SCK clock to the D/A converter (SPI interface)
//  
//    Analog Input   # 0  - center wiper pin of 5k ohm pot (heart rate adjust)
//
//    I followed the Timer2 setup as outlined by Sebastian Wallin 
//    http://popdevelop.com/2010/04/mastering-timer-interrupts-on-the-arduino/
//
//    I set up the SPI interface according to the excellent instructions of Australian John Boxall,
//    whose wonderful website has many excellent Arduino tutorials: 
//    http://tronixstuff.wordpress.com/
//
//  Programmer:  James P Lynch
//               lynch007@gmail.com
//  
// ***************************************************************************************************

#include "SPI.h"          // supports the SPI interface to the D/A converter and 7-segment display
#include <Wire.h>         // need the Wire library
#include <Arduino.h>

// various constants used by the waveform generator
#define INIT      0
#define IDLE      1
#define QRS       2
#define FOUR      4
#define THREE     3
#define TWO       2
#define ONE       1


// *******************************************************************************
//   y_data[543] - digitized ecg waveform, sampled at 1.0 msec
//
//   Waveform is scaled for a 12-bit D/A converter (0 .. 4096)
//
//   A 60 beat/min ECG would require this waveform (543 samples) plus 457 samples 
//   of the first y_data[0] value of 936.
//
// *******************************************************************************
const short  y_data[] = {
939, 940, 941, 942, 944, 945, 946, 947, 951, 956, 
962, 967, 973, 978, 983, 989, 994, 1000, 1005, 1015, 
1024, 1034, 1043, 1053, 1062, 1075, 1087, 1100, 1112, 1121, 
1126, 1131, 1136, 1141, 1146, 1151, 1156, 1164, 1172, 1179, 
1187, 1194, 1202, 1209, 1216, 1222, 1229, 1235, 1241, 1248, 
1254, 1260, 1264, 1268, 1271, 1275, 1279, 1283, 1287, 1286, 
1284, 1281, 1279, 1276, 1274, 1271, 1268, 1266, 1263, 1261, 
1258, 1256, 1253, 1251, 1246, 1242, 1237, 1232, 1227, 1222, 
1218, 1215, 1211, 1207, 1203, 1199, 1195, 1191, 1184, 1178, 
1171, 1165, 1159, 1152, 1146, 1141, 1136, 1130, 1125, 1120, 
1115, 1110, 1103, 1096, 1088, 1080, 1073, 1065, 1057, 1049, 
1040, 1030, 1021, 1012, 1004, 995, 987, 982, 978, 974, 
970, 966, 963, 959, 955, 952, 949, 945, 942, 939, 
938, 939, 940, 941, 943, 944, 945, 946, 946, 946, 
946, 946, 946, 946, 946, 947, 950, 952, 954, 956, 
958, 960, 962, 964, 965, 965, 965, 965, 965, 965, 
963, 960, 957, 954, 951, 947, 944, 941, 938, 932, 
926, 920, 913, 907, 901, 894, 885, 865, 820, 733, 
606, 555, 507, 632, 697, 752, 807, 896, 977, 1023, 
1069, 1127, 1237, 1347, 1457, 2085, 2246, 2474, 2549, 2595, 
2641, 2695, 3083, 3135, 3187, 3217, 3315, 3403, 3492, 3581, 
3804, 3847, 3890, 3798, 3443, 3453, 3297, 3053, 2819, 2810, 
2225, 2258, 1892, 1734, 1625, 998, 903, 355, 376, 203, 
30, 33, 61, 90, 119, 160, 238, 275, 292, 309, 
325, 343, 371, 399, 429, 484, 542, 602, 652, 703, 
758, 802, 838, 856, 875, 895, 917, 938, 967, 1016, 
1035, 1041, 1047, 1054, 1060, 1066, 1066, 1064, 1061, 1058, 
1056, 1053, 1051, 1048, 1046, 1043, 1041, 1038, 1035, 1033, 
1030, 1028, 1025, 1022, 1019, 1017, 1014, 1011, 1008, 1006, 
1003, 1001, 999, 998, 996, 994, 993, 991, 990, 988, 
986, 985, 983, 981, 978, 976, 973, 971, 968, 966, 
963, 963, 963, 963, 963, 963, 963, 963, 963, 963, 
963, 963, 963, 963, 963, 963, 963, 963, 963, 963, 
964, 965, 966, 967, 968, 969, 970, 971, 972, 974, 
976, 978, 980, 983, 985, 987, 989, 991, 993, 995, 
997, 999, 1002, 1006, 1011, 1015, 1019, 1023, 1028, 1032, 
1036, 1040, 1045, 1050, 1055, 1059, 1064, 1069, 1076, 1082, 
1088, 1095, 1101, 1107, 1114, 1120, 1126, 1132, 1141, 1149, 
1158, 1166, 1173, 1178, 1183, 1188, 1193, 1198, 1203, 1208, 
1214, 1221, 1227, 1233, 1240, 1246, 1250, 1254, 1259, 1263, 
1269, 1278, 1286, 1294, 1303, 1309, 1315, 1322, 1328, 1334, 
1341, 1343, 1345, 1347, 1349, 1351, 1353, 1355, 1357, 1359, 
1359, 1359, 1359, 1359, 1358, 1356, 1354, 1352, 1350, 1347, 
1345, 1343, 1341, 1339, 1336, 1334, 1332, 1329, 1327, 1324, 
1322, 1320, 1317, 1315, 1312, 1307, 1301, 1294, 1288, 1281, 
1275, 1270, 1265, 1260, 1256, 1251, 1246, 1240, 1233, 1227, 
1221, 1214, 1208, 1201, 1194, 1186, 1178, 1170, 1162, 1154, 
1148, 1144, 1140, 1136, 1131, 1127, 1123, 1118, 1114, 1107, 
1099, 1090, 1082, 1074, 1069, 1064, 1058, 1053, 1048, 1043, 
1038, 1034, 1029, 1025, 1021, 1017, 1013, 1009, 1005, 1001, 
997, 994, 990, 991, 992, 994, 996, 997, 999, 998, 
997, 996, 995, 994, 993, 991, 990, 989, 989, 989, 
989, 989, 989, 989, 988, 986, 984, 983, 981, 980, 
982, 984, 986, 988, 990, 993, 995, 997, 999, 1002, 
1005, 1008, 1012};

 
// global variables used by the program
unsigned int  NumSamples = sizeof(y_data) / 2;            // number of elements in y_data[] above
unsigned int  QRSCount = 0;                               // running QRS period msec count
unsigned int  IdleCount = 0;                              // running Idle period msec count
unsigned long IdlePeriod = 0;                             // idle period is adjusted by pot to set heart rate
unsigned int  State = INIT;                               // states are INIT, QRS, and IDLE
unsigned int  DisplayCount = 0;                           // counts 50 msec to update the 7-segment display
unsigned int  tcnt2;                                      // Timer2 reload value, globally available
float         BeatsPerMinute;                             // floating point representation of the heart rate
unsigned int  Bpm;                                        // integer version of heart rate (times 10)
unsigned int  BpmLow;                                     // lowest heart rate allowed (x10)
unsigned int  BpmHigh;                                    // highest heart rate allowed (x10)
int           Value;                                      // place holder for analog input 0
unsigned long BpmValues[32] = {0, 0, 0, 0, 0, 0, 0, 0,    // holds 32 last analog pot readings
                               0, 0, 0, 0, 0, 0, 0, 0,    // for use in filtering out display jitter
                               0, 0, 0, 0, 0, 0, 0, 0,    // for use in filtering out display jitter
                               0, 0, 0, 0, 0, 0, 0, 0};   // for use in filtering out display jitter
unsigned long BpmAverage = 0;                             // used in a simple averaging filter
unsigned char Index = 0;                                  // used in a simple averaging filter  
unsigned int  DisplayValue = 0;                           // filtered Beats Per Minute sent to display


void setup()   { 
  
  // Configure the output ports (1 msec intrerrupt indicator and D/A SPI support)
  pinMode(9, OUTPUT);                     // 7-segment display chip select   (low to select chip) 
  pinMode(10, OUTPUT);                    // D/A converter chip select       (low to select chip) 
  pinMode(11, OUTPUT);                    // SDI data 
  pinMode(13, OUTPUT);                    // SCK clock

  // initial state of SPI interface
  SPI.begin();                            // wake up the SPI bus.
  SPI.setDataMode(0);                     // mode: CPHA=0, data captured on clock's rising edge (low→high)
  SPI.setClockDivider(SPI_CLOCK_DIV64);   // system clock / 64
  SPI.setBitOrder(MSBFIRST);              // bit 7 clocks out first
  
  // establish the heart rate range allowed
  // BpmLow  = 300 (30 bpm x 10)
  // BpmHigh = (60.0 / (NumSamples * 0.001)) * 10  = (60.0 / .543) * 10 = 1104  (110.49 x 10)
  BpmLow = 300;
  BpmHigh = (60.0 / ((float)NumSamples * 0.001)) * 10;

  // First disable the timer overflow interrupt while we're configuring
  TIMSK2 &=~(1<<TOIE2);

  // Configure timer2 in normal mode (pure counting, no PWM etc.)
  TCCR2A &= ~((1<<WGM21) | (1<<WGM20));
  TCCR2B &= ~(1<<WGM22);

  // Select clock source: internal I/O clock   
  ASSR &= ~(1<<AS2);

  // Disable Compare Match A interrupt enable (only want overflow)   
  TIMSK2 &= ~(1<<OCIE2A);

  // Now configure the prescaler to CPU clock divided by 128   
  TCCR2B |= (1<<CS22)  | (1<<CS20); // Set bits
  TCCR2B &= ~(1<<CS21);             // Clear bit

  // We need to calculate a proper value to load the timer counter.
  // The following loads the value 131 into the Timer 2 counter register
  // The math behind this is:
  // (CPU frequency) / (prescaler value) = 125000 Hz = 8us.
  // (desired period) / 8us = 125.
  // MAX(uint8) + 1 - 125 = 131;
  // 
  // Save value globally for later reload in ISR  /
  tcnt2 = 131; 

  // Finally load end enable the timer   
  TCNT2 = tcnt2;
  TIMSK2 |= (1<<TOIE2);
}


void loop() {

  // read from the heart rate pot (Analog Input 0)  
  Value = analogRead(0);
 
  // map the Analog Input 0 range (0 .. 1023) to the Bpm range (300 .. 1104)
  Bpm = map(Value, 0, 1023, BpmLow, BpmHigh);

  // To lessen the jitter or bounce in the display's least significant digit,
  // a moving average filter (32 values) will smooth it out.
  BpmValues[Index++] = Bpm;                       // add latest sample to eight element array
  if (Index == 32) {                              // handle wrap-around
    Index = 0;
  }
  BpmAverage = 0;
  for (int  i = 0;  i < 32; i++) {                // summation of all values in the array
    BpmAverage += BpmValues[i];
  }
  BpmAverage >>= 5;                               // Divide by 32 to get average
  
  // now update the 4-digit display - format: XXX.X
  // since update is a multi-byte transfer, disable interrupts until it's done
  noInterrupts();
  DisplayValue = BpmAverage;
  interrupts();  
  
  // given the pot value (beats per minute) read in, calculate the IdlePeriod (msec)
  // this value is used by the Timer2 1.0 msec interrupt service routine
  BeatsPerMinute = (float)Bpm / 10.0;
  noInterrupts();
  IdlePeriod = (unsigned int)((float)60000.0 / BeatsPerMinute) - (float)NumSamples;
  interrupts();
  
  delay(20);
}






// ********************************************************************************
//                Timer2 Interrupt Service Routine
//
// Interrupt Service Routine (ISR) for Timer2 overflow at 1.000 msec.
//
//
// The Timer2 interrupt function is used to send the 16-bit waveform point
// to the Microchip MCP4921 D/A converter using the SPI interface. 
//
// The Timer2 interrupt function is also used to send the current heart rate
// as read from the potentiometer every 50 Timer2 interrupts to the 7-segment display.
//
// The pot is read and the heart rate is calculated in the background loop. 
// By running both SPI peripherals at interrupt level, we "serialize" them and avoid 
// corruption by one SPI transmission being interrupted by the other.
//
// A state machime is implemented to accomplish this. It's states are:
//
//       INIT -    basically clears the counters and sets the state to QRS.
//
//       QRS  -    outputs the next ECG waveform data point every 1.0 msec
//                 there are 543 of these QRS complex data points.
//
//       IDLE -    variable period after the QRS part.
//                 D/A holds first ECG value (936) for all of the IDLE period.
//                 Idle period varies to allow adjustment of the basic heart rate; 
//                 a value of zero msec for the idle period gives 110.4 beats per min 
//                 while the maximum idle period of 457 msec gives 30.0 bpm.
//
//                 Note that the IDLE period is calculated in the main background 
//                 loop by reading a pot and converting its range to one suitable
//                 for the background period. The interrupt routine reads this 
//                 value to determine when to stop the IDLE period.
//
// The transmission of the next data point to the D/A converter via SPI takes
// about 63 microseconds (that includes two SPI byte transmissions).
//
// The transmission of the heart rate digits to the Sparkfun 7-segment display
// takes about 350 usec (it is only transmitted every 50 Timer2 interrupts)
//
// ********************************************************************************
ISR(TIMER2_OVF_vect) {
  
  // Reload the timer   
  TCNT2 = tcnt2;
  
  // state machine
  switch (State) {
    
    case INIT:

      // zero the QRS and IDLE counters
      QRSCount = 0;
      IdleCount = 0;
      DisplayCount = 0;
    
      // set next state to QRS  
      State = QRS;
    break;
  
    case QRS:

      // output the next sample in the QRS waveform to the D/A converter 
      DTOA_Send(y_data[QRSCount]);
      
      // advance sample counter and check for end
      QRSCount++;
      if (QRSCount >= NumSamples) {
        // start IDLE period and output first sample to DTOA
        QRSCount = 0;
        DTOA_Send(y_data[0]);
        State = IDLE;
      }
    break;
  
    case IDLE:

      // since D/A converter will hold the previous value written, all we have
      // to do is determine how long the IDLE period should be.
    
      // advance idle counter and check for end
      IdleCount++;
      
      // the IdlePeriod is calculated in the main loop (from a pot)
      if (IdleCount >= IdlePeriod) {
        IdleCount = 0;
        State = QRS;
      }  
    break;
  
    default:
    break;
  }
 
  // output to the 7-segment display every 50 msec
  DisplayCount++;
  if (DisplayCount >= 50) {
    DisplayCount = 0;
    Display7Seg_Send(DisplayValue);
  } 
}  




// ***************************************************************************************************
//                      void  DTOA_Send(unsigned short)
//
//  Purpose: send 12-bit D/A value to Microchip MCP4921 D/A converter ( 0 .. 4096 )
//
//
//  Input:    DtoAValue - 12-bit D/A value ( 0 .. 4096 )
//
//
//            The DtoAValue is prepended with the A/B, BUF, GA, and SHDN bits before transmission.
//
//                                           WRITE COMMAND
//            |--------|--------|--------|--------|--------------------------------------------------|
//            |  A/B   |   BUF  |  GA    | SHDN   | D11 D10 D09 D08 D07 D06 D05 D04 D03 D02 D01 D00  |
//            |        |        |        |        |                                                  |
//            |setting:|setting:|setting:|Setting:|            DtoAValue   (12 bits)                 |
//            |   0    |   0    |   1    |   1    |                                                  |
//            | DAC-A  |unbuffer|   1x   |power-on| ( 0 .. 4096  will output as 0 volts .. 5 volts ) |
//            |--------|--------|--------|--------|--------------------------------------------------|
//                15      14        13       12    11                                               0
//  To D/A    <======================================================================================
//
//            Note:  WriteCommand is clocked out with bit 15 first!
//
//
//  Returns:  nothing
//
//
//  I/O Resources:  Digital Pin 9  = chip select (low to select chip)
//                  Digital Pin 13 = SPI Clock
//                  Digital Pin 11 = SPI Data 
//
//  Note: by grounding the LDAC* pin in the hardware hook-up, the SPI data will be clocked into the 
//        D/A converter latches when the chip select rises at the end-of-transfer.
//
//        This routine takes 63 usec using an Adafruit Menta
// ***************************************************************************************************
void  DTOA_Send(unsigned short DtoAValue) {
  
  byte            Data = 0;
  // select the D/A chip (low)
  digitalWrite(10, 0);    // chip select low
  
  // send the high byte first 0011xxxx
  Data = highByte(DtoAValue);
  Data = 0b00001111 & Data;
  Data = 0b00110000 | Data;
  SPI.transfer(Data);
  
  // send the low byte next xxxxxxxx
  Data = lowByte(DtoAValue);
  SPI.transfer(Data);
 
  // all done, de-select the chip (this updates the D/A with the new value) 
  digitalWrite(10, 1);    // chip select high
}





// ***************************************************************************************************
//                      void  Display7Seg_Send(char *)
//
//  Purpose: send 4 digits to SparkFun Serial 7-Segment Display (requires 4 SPI writes)
//
//  Input:    value -  unsigned int version of BeatsPerMinute
//
//  Returns:  nothing
//
//  I/O Resources:  Digital Pin 10 = chip select (low to select chip)
//                  Digital Pin 13 = SPI Clock
//                  Digital Pin 11 = SPI Data 
//
//  Note:  this routine takes 350 usec using an Adafruit Menta
// ***************************************************************************************************
void  Display7Seg_Send(unsigned int  HeartRate) {
  uint8_t       digit1, digit2, digit3, digit4;
  unsigned int  value;
  
  // convert to four digits (set leading zeros to blanks; 0x78 is the blank character)
  value = HeartRate;
  digit1 = value / 1000;
  value -= digit1 * 1000;
  if (digit1 == 0) digit1 = 0x78;

  digit2 = value / 100;
  value -= digit2 * 100;
  if ((digit1 == 0x78) && (digit2 == 0)) digit2 = 0x78;

  digit3 = value / 10;
  value -= digit3 * 10;
  if ((digit1 == 0x78) && (digit2 == 0x78) && (digit3 == 0)) digit3 = 0x78;

  digit4 = value;

  digitalWrite(9, LOW);    // select the Sparkfun 7-seg display
  SPI.transfer(0x76);      // reset display
  SPI.transfer(0x7A);      // brightness command
  SPI.transfer(0x00);      // 0 = bright,  255 = dim
  SPI.transfer(digit1);    // Thousands Digit
  SPI.transfer(digit2);    // Hundreds Digit
  SPI.transfer(digit3);    // Tens Digit
  SPI.transfer(digit4);    // Ones Digit
  SPI.transfer(0x77);      // set decimal points command
  SPI.transfer(0x04);      // turn on dec pt between digits 3 and 4
  digitalWrite(9, HIGH);   // release Sparkfun 7-seg display
}

Merci à tous.

hello
ton prog lit un potentiomètre de 0 à 1023 puis "map" la valeur lue entre 300 et 1024
le résultat est sensé être les battements du cœur par minute (BPM).
je suis surpris de ces valeurs pour simuler une fréquence cardiaque.

pour ton afficheur, tu peux aisément adapter ton code
sur le lien que tu donnes, il y a des exemples.

à la reflexion, je me demande si l'I2C de l'afficheur ne sera pas perturber par l'ISP

I2C sur A4 et A5 si tu n'es pas sur de ton cablage ( ordre des fils) passe un scan I2C
à tester

// ***************************************************************************************************
//                                    ECG SIMULATOR
//
//  Purpose: simulate the normal sinus rhythm ECG signal (3-leads RA,LA,RL)
//
//  Background:
//
//  In normal electrocardiography (ECG or EKG if you're German), three leads make up the
//  Einthoven's triangle. Two leads are taped to the right and left side of the chest above
//  the heart (RA = right arm, LA = left arm) and one lead is taped to the lower chest, typically
//  on the right hip (RL = right leg).
//
//  It's important to know that these ECG signals are millivolts in amplitude. This can be achieved by
//  feeding the D/A converter through a voltage divider to get to the millivolt levels.
//
//
//  The ECG signal:
//
//  I found a suitable ECG waveform from the internet. Here is how I converted a picture from my
//  monitor screen to a C language array of A/D values, each spaced 1.00 msec apart.
//
//       A. Screen shot of waveform using the free screen capture program MWSNAP
//                http://www.mirekw.com/winfreeware/mwsnap.html
//
//       B. Digitize the jpeg waveform using the free digitizing program ENGAUGE
//                http://digitizer.sourceforge.net/
//
//
//       C: I wrote a Python program to convert the rather irregular samples from ENGAUGE
//          to an array of values spaced 1.0 milliseconds apart using linear interpolation.
//          Then I created a text file where these data points were part of a C language array
//          construct; that is, the data points are C initializers.
//
//       D: Cut-and-paste from the text file the C data array with initializers into the
//          Arduino sketch below.
//
//
//  Arduino Resources:
//
//    Digital Output # 9  - chip select the 7-segment display SPI port (low to select)
//    Digital Output # 10 - chip select for D/A converter  (low to select)
//    Digital Output # 11 - SDI data to the D/A converter  (SPI interface)
//    Digital Output # 13 - SCK clock to the D/A converter (SPI interface)
//
//    Analog Input   # 0  - center wiper pin of 5k ohm pot (heart rate adjust)
//
//    I followed the Timer2 setup as outlined by Sebastian Wallin
//    http://popdevelop.com/2010/04/mastering-timer-interrupts-on-the-arduino/
//
//    I set up the SPI interface according to the excellent instructions of Australian John Boxall,
//    whose wonderful website has many excellent Arduino tutorials:
//    http://tronixstuff.wordpress.com/
//
//  Programmer:  James P Lynch
//               lynch007@gmail.com
//
// ***************************************************************************************************

#include "SPI.h"          // supports the SPI interface to the D/A converter and 7-segment display
#include <Wire.h>         // need the Wire library
#include <Arduino.h>
// µµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµ
#include "rgb_lcd.h"
rgb_lcd lcd;
const int colorR = 255;
const int colorG = 0;
const int colorB = 0;
// µµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµ
// various constants used by the waveform generator
#define INIT      0
#define IDLE      1
#define QRS       2
#define FOUR      4
#define THREE     3
#define TWO       2
#define ONE       1


// *******************************************************************************
//   y_data[543] - digitized ecg waveform, sampled at 1.0 msec
//
//   Waveform is scaled for a 12-bit D/A converter (0 .. 4096)
//
//   A 60 beat/min ECG would require this waveform (543 samples) plus 457 samples
//   of the first y_data[0] value of 936.
//
// *******************************************************************************
const short  y_data[] = {
  939, 940, 941, 942, 944, 945, 946, 947, 951, 956,
  962, 967, 973, 978, 983, 989, 994, 1000, 1005, 1015,
  1024, 1034, 1043, 1053, 1062, 1075, 1087, 1100, 1112, 1121,
  1126, 1131, 1136, 1141, 1146, 1151, 1156, 1164, 1172, 1179,
  1187, 1194, 1202, 1209, 1216, 1222, 1229, 1235, 1241, 1248,
  1254, 1260, 1264, 1268, 1271, 1275, 1279, 1283, 1287, 1286,
  1284, 1281, 1279, 1276, 1274, 1271, 1268, 1266, 1263, 1261,
  1258, 1256, 1253, 1251, 1246, 1242, 1237, 1232, 1227, 1222,
  1218, 1215, 1211, 1207, 1203, 1199, 1195, 1191, 1184, 1178,
  1171, 1165, 1159, 1152, 1146, 1141, 1136, 1130, 1125, 1120,
  1115, 1110, 1103, 1096, 1088, 1080, 1073, 1065, 1057, 1049,
  1040, 1030, 1021, 1012, 1004, 995, 987, 982, 978, 974,
  970, 966, 963, 959, 955, 952, 949, 945, 942, 939,
  938, 939, 940, 941, 943, 944, 945, 946, 946, 946,
  946, 946, 946, 946, 946, 947, 950, 952, 954, 956,
  958, 960, 962, 964, 965, 965, 965, 965, 965, 965,
  963, 960, 957, 954, 951, 947, 944, 941, 938, 932,
  926, 920, 913, 907, 901, 894, 885, 865, 820, 733,
  606, 555, 507, 632, 697, 752, 807, 896, 977, 1023,
  1069, 1127, 1237, 1347, 1457, 2085, 2246, 2474, 2549, 2595,
  2641, 2695, 3083, 3135, 3187, 3217, 3315, 3403, 3492, 3581,
  3804, 3847, 3890, 3798, 3443, 3453, 3297, 3053, 2819, 2810,
  2225, 2258, 1892, 1734, 1625, 998, 903, 355, 376, 203,
  30, 33, 61, 90, 119, 160, 238, 275, 292, 309,
  325, 343, 371, 399, 429, 484, 542, 602, 652, 703,
  758, 802, 838, 856, 875, 895, 917, 938, 967, 1016,
  1035, 1041, 1047, 1054, 1060, 1066, 1066, 1064, 1061, 1058,
  1056, 1053, 1051, 1048, 1046, 1043, 1041, 1038, 1035, 1033,
  1030, 1028, 1025, 1022, 1019, 1017, 1014, 1011, 1008, 1006,
  1003, 1001, 999, 998, 996, 994, 993, 991, 990, 988,
  986, 985, 983, 981, 978, 976, 973, 971, 968, 966,
  963, 963, 963, 963, 963, 963, 963, 963, 963, 963,
  963, 963, 963, 963, 963, 963, 963, 963, 963, 963,
  964, 965, 966, 967, 968, 969, 970, 971, 972, 974,
  976, 978, 980, 983, 985, 987, 989, 991, 993, 995,
  997, 999, 1002, 1006, 1011, 1015, 1019, 1023, 1028, 1032,
  1036, 1040, 1045, 1050, 1055, 1059, 1064, 1069, 1076, 1082,
  1088, 1095, 1101, 1107, 1114, 1120, 1126, 1132, 1141, 1149,
  1158, 1166, 1173, 1178, 1183, 1188, 1193, 1198, 1203, 1208,
  1214, 1221, 1227, 1233, 1240, 1246, 1250, 1254, 1259, 1263,
  1269, 1278, 1286, 1294, 1303, 1309, 1315, 1322, 1328, 1334,
  1341, 1343, 1345, 1347, 1349, 1351, 1353, 1355, 1357, 1359,
  1359, 1359, 1359, 1359, 1358, 1356, 1354, 1352, 1350, 1347,
  1345, 1343, 1341, 1339, 1336, 1334, 1332, 1329, 1327, 1324,
  1322, 1320, 1317, 1315, 1312, 1307, 1301, 1294, 1288, 1281,
  1275, 1270, 1265, 1260, 1256, 1251, 1246, 1240, 1233, 1227,
  1221, 1214, 1208, 1201, 1194, 1186, 1178, 1170, 1162, 1154,
  1148, 1144, 1140, 1136, 1131, 1127, 1123, 1118, 1114, 1107,
  1099, 1090, 1082, 1074, 1069, 1064, 1058, 1053, 1048, 1043,
  1038, 1034, 1029, 1025, 1021, 1017, 1013, 1009, 1005, 1001,
  997, 994, 990, 991, 992, 994, 996, 997, 999, 998,
  997, 996, 995, 994, 993, 991, 990, 989, 989, 989,
  989, 989, 989, 989, 988, 986, 984, 983, 981, 980,
  982, 984, 986, 988, 990, 993, 995, 997, 999, 1002,
  1005, 1008, 1012
};


// global variables used by the program
unsigned int  NumSamples = sizeof(y_data) / 2;            // number of elements in y_data[] above
unsigned int  QRSCount = 0;                               // running QRS period msec count
unsigned int  IdleCount = 0;                              // running Idle period msec count
unsigned long IdlePeriod = 0;                             // idle period is adjusted by pot to set heart rate
unsigned int  State = INIT;                               // states are INIT, QRS, and IDLE
unsigned int  DisplayCount = 0;                           // counts 50 msec to update the 7-segment display
unsigned int  tcnt2;                                      // Timer2 reload value, globally available
float         BeatsPerMinute;                             // floating point representation of the heart rate
unsigned int  Bpm;                                        // integer version of heart rate (times 10)
unsigned int  BpmLow;                                     // lowest heart rate allowed (x10)
unsigned int  BpmHigh;                                    // highest heart rate allowed (x10)
int           Value;                                      // place holder for analog input 0
unsigned long BpmValues[32] = {0, 0, 0, 0, 0, 0, 0, 0,    // holds 32 last analog pot readings
                               0, 0, 0, 0, 0, 0, 0, 0,    // for use in filtering out display jitter
                               0, 0, 0, 0, 0, 0, 0, 0,    // for use in filtering out display jitter
                               0, 0, 0, 0, 0, 0, 0, 0
                              };   // for use in filtering out display jitter
unsigned long BpmAverage = 0;                             // used in a simple averaging filter
unsigned char Index = 0;                                  // used in a simple averaging filter
unsigned int  DisplayValue = 0;                           // filtered Beats Per Minute sent to display


void setup()   {
// µµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµ
  Serial.begin(115200);
// µµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµ
  // Configure the output ports (1 msec intrerrupt indicator and D/A SPI support)
  pinMode(9, OUTPUT);                     // 7-segment display chip select   (low to select chip)
  pinMode(10, OUTPUT);                    // D/A converter chip select       (low to select chip)
  pinMode(11, OUTPUT);                    // SDI data
  pinMode(13, OUTPUT);                    // SCK clock
  
// µµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµ
lcd.begin(16, 2);
lcd.setRGB(colorR, colorG, colorB);
// µµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµ

  // initial state of SPI interface
  SPI.begin();                            // wake up the SPI bus.
  SPI.setDataMode(0);                     // mode: CPHA=0, data captured on clock's rising edge (low→high)
  SPI.setClockDivider(SPI_CLOCK_DIV64);   // system clock / 64
  SPI.setBitOrder(MSBFIRST);              // bit 7 clocks out first

  // establish the heart rate range allowed
  // BpmLow  = 300 (30 bpm x 10)
  // BpmHigh = (60.0 / (NumSamples * 0.001)) * 10  = (60.0 / .543) * 10 = 1104  (110.49 x 10)
  BpmLow = 300;
  BpmHigh = (60.0 / ((float)NumSamples * 0.001)) * 10;

  // First disable the timer overflow interrupt while we're configuring
  TIMSK2 &= ~(1 << TOIE2);

  // Configure timer2 in normal mode (pure counting, no PWM etc.)
  TCCR2A &= ~((1 << WGM21) | (1 << WGM20));
  TCCR2B &= ~(1 << WGM22);

  // Select clock source: internal I/O clock
  ASSR &= ~(1 << AS2);

  // Disable Compare Match A interrupt enable (only want overflow)
  TIMSK2 &= ~(1 << OCIE2A);

  // Now configure the prescaler to CPU clock divided by 128
  TCCR2B |= (1 << CS22)  | (1 << CS20); // Set bits
  TCCR2B &= ~(1 << CS21);           // Clear bit

  // We need to calculate a proper value to load the timer counter.
  // The following loads the value 131 into the Timer 2 counter register
  // The math behind this is:
  // (CPU frequency) / (prescaler value) = 125000 Hz = 8us.
  // (desired period) / 8us = 125.
  // MAX(uint8) + 1 - 125 = 131;
  //
  // Save value globally for later reload in ISR  /
  tcnt2 = 131;

  // Finally load end enable the timer
  TCNT2 = tcnt2;
  TIMSK2 |= (1 << TOIE2);
}


void loop() {

  // read from the heart rate pot (Analog Input 0)
  Value = analogRead(A0);

  // map the Analog Input 0 range (0 .. 1023) to the Bpm range (300 .. 1104)
  Bpm = map(Value, 0, 1023, BpmLow, BpmHigh);

  // To lessen the jitter or bounce in the display's least significant digit,
  // a moving average filter (32 values) will smooth it out.
  BpmValues[Index++] = Bpm;                       // add latest sample to eight element array
  if (Index == 32) {                              // handle wrap-around
    Index = 0;
  }
  BpmAverage = 0;
  for (int  i = 0;  i < 32; i++) {                // summation of all values in the array
    BpmAverage += BpmValues[i];
  }
  BpmAverage >>= 5;                               // Divide by 32 to get average

  // now update the 4-digit display - format: XXX.X
  // since update is a multi-byte transfer, disable interrupts until it's done
  noInterrupts();
  DisplayValue = BpmAverage;
  interrupts();

  // given the pot value (beats per minute) read in, calculate the IdlePeriod (msec)
  // this value is used by the Timer2 1.0 msec interrupt service routine
  BeatsPerMinute = (float)Bpm / 10.0;
  noInterrupts();
  IdlePeriod = (unsigned int)((float)60000.0 / BeatsPerMinute) - (float)NumSamples;
  interrupts();

  delay(20);
}






// ********************************************************************************
//                Timer2 Interrupt Service Routine
//
// Interrupt Service Routine (ISR) for Timer2 overflow at 1.000 msec.
//
//
// The Timer2 interrupt function is used to send the 16-bit waveform point
// to the Microchip MCP4921 D/A converter using the SPI interface.
//
// The Timer2 interrupt function is also used to send the current heart rate
// as read from the potentiometer every 50 Timer2 interrupts to the 7-segment display.
//
// The pot is read and the heart rate is calculated in the background loop.
// By running both SPI peripherals at interrupt level, we "serialize" them and avoid
// corruption by one SPI transmission being interrupted by the other.
//
// A state machime is implemented to accomplish this. It's states are:
//
//       INIT -    basically clears the counters and sets the state to QRS.
//
//       QRS  -    outputs the next ECG waveform data point every 1.0 msec
//                 there are 543 of these QRS complex data points.
//
//       IDLE -    variable period after the QRS part.
//                 D/A holds first ECG value (936) for all of the IDLE period.
//                 Idle period varies to allow adjustment of the basic heart rate;
//                 a value of zero msec for the idle period gives 110.4 beats per min
//                 while the maximum idle period of 457 msec gives 30.0 bpm.
//
//                 Note that the IDLE period is calculated in the main background
//                 loop by reading a pot and converting its range to one suitable
//                 for the background period. The interrupt routine reads this
//                 value to determine when to stop the IDLE period.
//
// The transmission of the next data point to the D/A converter via SPI takes
// about 63 microseconds (that includes two SPI byte transmissions).
//
// The transmission of the heart rate digits to the Sparkfun 7-segment display
// takes about 350 usec (it is only transmitted every 50 Timer2 interrupts)
//
// ********************************************************************************
ISR(TIMER2_OVF_vect) 
{
  // Reload the timer
  TCNT2 = tcnt2;
  // state machine
  switch (State)
  {
    case INIT:

      // zero the QRS and IDLE counters
      QRSCount = 0;
      IdleCount = 0;
      DisplayCount = 0;

      // set next state to QRS
      State = QRS;
      break;

    case QRS:

      // output the next sample in the QRS waveform to the D/A converter
      DTOA_Send(y_data[QRSCount]);

      // advance sample counter and check for end
      QRSCount++;
      if (QRSCount >= NumSamples) {
        // start IDLE period and output first sample to DTOA
        QRSCount = 0;
        DTOA_Send(y_data[0]);
        State = IDLE;
      }
      break;

    case IDLE:

      // since D/A converter will hold the previous value written, all we have
      // to do is determine how long the IDLE period should be.

      // advance idle counter and check for end
      IdleCount++;

      // the IdlePeriod is calculated in the main loop (from a pot)
      if (IdleCount >= IdlePeriod) {
        IdleCount = 0;
        State = QRS;
      }
      break;

    default:
      break;
  }

  // output to the 7-segment display every 50 msec
  DisplayCount++;
  if (DisplayCount >= 50) {
    DisplayCount = 0;
    Display7Seg_Send(DisplayValue);
  }
}




// ***************************************************************************************************
//                      void  DTOA_Send(unsigned short)
//
//  Purpose: send 12-bit D/A value to Microchip MCP4921 D/A converter ( 0 .. 4096 )
//
//
//  Input:    DtoAValue - 12-bit D/A value ( 0 .. 4096 )
//
//
//            The DtoAValue is prepended with the A/B, BUF, GA, and SHDN bits before transmission.
//
//                                           WRITE COMMAND
//            |--------|--------|--------|--------|--------------------------------------------------|
//            |  A/B   |   BUF  |  GA    | SHDN   | D11 D10 D09 D08 D07 D06 D05 D04 D03 D02 D01 D00  |
//            |        |        |        |        |                                                  |
//            |setting:|setting:|setting:|Setting:|            DtoAValue   (12 bits)                 |
//            |   0    |   0    |   1    |   1    |                                                  |
//            | DAC-A  |unbuffer|   1x   |power-on| ( 0 .. 4096  will output as 0 volts .. 5 volts ) |
//            |--------|--------|--------|--------|--------------------------------------------------|
//                15      14        13       12    11                                               0
//  To D/A    <======================================================================================
//
//            Note:  WriteCommand is clocked out with bit 15 first!
//
//
//  Returns:  nothing
//
//
//  I/O Resources:  Digital Pin 9  = chip select (low to select chip)
//                  Digital Pin 13 = SPI Clock
//                  Digital Pin 11 = SPI Data
//
//  Note: by grounding the LDAC* pin in the hardware hook-up, the SPI data will be clocked into the
//        D/A converter latches when the chip select rises at the end-of-transfer.
//
//        This routine takes 63 µ sec using an Adafruit Menta
// ***************************************************************************************************
void  DTOA_Send(unsigned short DtoAValue) {

  byte            Data = 0;
  // select the D/A chip (low)
  digitalWrite(10, 0);    // chip select low

  // send the high byte first 0011xxxx
  Data = highByte(DtoAValue);
  Data = 0b00001111 & Data;
  Data = 0b00110000 | Data;
  SPI.transfer(Data);

  // send the low byte next xxxxxxxx
  Data = lowByte(DtoAValue);
  SPI.transfer(Data);

  // all done, de-select the chip (this updates the D/A with the new value)
  digitalWrite(10, 1);    // chip select high
}





// ***************************************************************************************************
//                      void  Display7Seg_Send(char *)
//
//  Purpose: send 4 digits to SparkFun Serial 7-Segment Display (requires 4 SPI writes)
//
//  Input:    value -  unsigned int version of BeatsPerMinute
//
//  Returns:  nothing
//
//  I/O Resources:  Digital Pin 10 = chip select (low to select chip)
//                  Digital Pin 13 = SPI Clock
//                  Digital Pin 11 = SPI Data
//
//  Note:  this routine takes 350 usec using an Adafruit Menta
// ***************************************************************************************************
void  Display7Seg_Send(unsigned int  HeartRate) {
  
// µµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµ
  Serial.print(HeartRate);
  lcd.setCursor(0, 0);
  lcd.print(HeartRate);
// µµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµ

/*
  uint8_t       digit1, digit2, digit3, digit4;
  unsigned int  value;

  // convert to four digits (set leading zeros to blanks; 0x78 is the blank character)
  value = HeartRate;
  digit1 = value / 1000;
  value -= digit1 * 1000;
  if (digit1 == 0) digit1 = 0x78;

  digit2 = value / 100;
  value -= digit2 * 100;
  if ((digit1 == 0x78) && (digit2 == 0)) digit2 = 0x78;

  digit3 = value / 10;
  value -= digit3 * 10;
  if ((digit1 == 0x78) && (digit2 == 0x78) && (digit3 == 0)) digit3 = 0x78;

  digit4 = value;

  digitalWrite(9, LOW);    // select the Sparkfun 7-seg display
  SPI.transfer(0x76);      // reset display
  SPI.transfer(0x7A);      // brightness command
  SPI.transfer(0x00);      // 0 = bright,  255 = dim
  Serial.print('\t');
  Serial.print(digit1); Serial.print('\t');
  SPI.transfer(digit1);    // Thousands Digit
  Serial.print(digit2); Serial.print('\t');
  SPI.transfer(digit2);    // Hundreds Digit
  Serial.print(digit3); Serial.print('\t');
  SPI.transfer(digit3);    // Tens Digit
  Serial.print(digit4); Serial.println("");
  SPI.transfer(digit4);    // Ones Digit
  SPI.transfer(0x77);      // set decimal points command
  SPI.transfer(0x04);      // turn on dec pt between digits 3 and 4
  digitalWrite(9, HIGH);   // release Sparkfun 7-seg display
  */
}

J’avoue que je ne me suis pas poser la question du « comment ». Mais ça fonctionne à part l’affichage de la valeur !

Si j’ai bien compris le fonctionnement, on génère le signal ECG et avec le potentiomètre, on fait varier la fréquence à laquelle il est répété.

Du coup, si je comprend bien je dois modifier uniquement cette partie du code ? Et comment ça se branche par rapport à l’écran 7 segments ?


void  Display7Seg_Send(unsigned int  HeartRate) {
  uint8_t       digit1, digit2, digit3, digit4;
  unsigned int  value;
  
  // convert to four digits (set leading zeros to blanks; 0x78 is the blank character)
  value = HeartRate;
  digit1 = value / 1000;
  value -= digit1 * 1000;
  if (digit1 == 0) digit1 = 0x78;

  digit2 = value / 100;
  value -= digit2 * 100;
  if ((digit1 == 0x78) && (digit2 == 0)) digit2 = 0x78;

  digit3 = value / 10;
  value -= digit3 * 10;
  if ((digit1 == 0x78) && (digit2 == 0x78) && (digit3 == 0)) digit3 = 0x78;

  digit4 = value;

  digitalWrite(9, LOW);    // select the Sparkfun 7-seg display
  SPI.transfer(0x76);      // reset display
  SPI.transfer(0x7A);      // brightness command
  SPI.transfer(0x00);      // 0 = bright,  255 = dim
  SPI.transfer(digit1);    // Thousands Digit
  SPI.transfer(digit2);    // Hundreds Digit
  SPI.transfer(digit3);    // Tens Digit
  SPI.transfer(digit4);    // Ones Digit
  SPI.transfer(0x77);      // set decimal points command
  SPI.transfer(0x04);      // turn on dec pt between digits 3 and 4
  digitalWrite(9, HIGH);   // release Sparkfun 7-seg display
}

Je vais étudier ça un peu plus demain matin.

Merci.

le code en #15 est modifié pour utiliser ton lcd.
ton lcd est en I2C il a besoin de GND, Vcc, SDA, et SCL
SDA et SCL sont A4 et A5 sur la platine arduino
si tu as inversé les fils sur SDA et SCL, le lcd ne sera pas vu.
il faudra alors inverser les deux fils. et si tu n'as toujours rien, il te faudra passer un scaner I2C pour t'assurer qu'il est bien branché. tu pourras aussi charger un programme d'exemple pour t'assurer de son bon fonctionnement. par exemple "hello word".
une fois que tu seras sur du bon fonctionnement de ton lcd, tu pourras tester en chargeant le prog du post #15.
et c'est là que j'ai un doute sur la compatibilité de l'usage du SPI et de l'I2C en même temps.

si tu n'as pas le droit d'utiliser les deux modes de liaison, il te sera possible de prendre un afficheur ayant pour µ un 7219 ( qui gère le SPI.

Merci pour toutes ces infos ! Merci pour le code. En effet, ce n'est pas très compliqué.

J'ai tout mis en place. L'écran fonctionne mais aucune valeur ne s'affiche.
C'est certainement lié à un conflit SPI/I2C.

Pourtant selon le sujet suivant, cela devrait pouvoir fonctionner : [Résolu] Cohabitation SPI et I2C. Je pense donc à un problème dans le code.

J'ai réussi. On repassera pour la manière mais ça fonctionne.
J'ai "retiré" tout le code concernant l'envoi vers l'affichage et j'ai rajouté ces quelques lignes dans le loop() :

  lcd.clear();
  lcd.setCursor(0, 0);
  lcd.print("BPM : ");
  lcd.setCursor(0, 1);
  lcd.print(DisplayValue/10);

Voilà ce que ça donne au final :

// ***************************************************************************************************
//                                    ECG SIMULATOR
//
//  Purpose: simulate the normal sinus rhythm ECG signal (3-leads RA,LA,RL)
//
//  Background:
//
//  In normal electrocardiography (ECG or EKG if you're German), three leads make up the
//  Einthoven's triangle. Two leads are taped to the right and left side of the chest above
//  the heart (RA = right arm, LA = left arm) and one lead is taped to the lower chest, typically
//  on the right hip (RL = right leg).
//
//  It's important to know that these ECG signals are millivolts in amplitude. This can be achieved by
//  feeding the D/A converter through a voltage divider to get to the millivolt levels.
//
//
//  The ECG signal:
//
//  I found a suitable ECG waveform from the internet. Here is how I converted a picture from my
//  monitor screen to a C language array of A/D values, each spaced 1.00 msec apart.
//
//       A. Screen shot of waveform using the free screen capture program MWSNAP
//                http://www.mirekw.com/winfreeware/mwsnap.html
//
//       B. Digitize the jpeg waveform using the free digitizing program ENGAUGE
//                http://digitizer.sourceforge.net/
//
//
//       C: I wrote a Python program to convert the rather irregular samples from ENGAUGE
//          to an array of values spaced 1.0 milliseconds apart using linear interpolation.
//          Then I created a text file where these data points were part of a C language array
//          construct; that is, the data points are C initializers.
//
//       D: Cut-and-paste from the text file the C data array with initializers into the
//          Arduino sketch below.
//
//
//  Arduino Resources:
//
//    Digital Output # 9  - chip select the 7-segment display SPI port (low to select)
//    Digital Output # 10 - chip select for D/A converter  (low to select)
//    Digital Output # 11 - SDI data to the D/A converter  (SPI interface)
//    Digital Output # 13 - SCK clock to the D/A converter (SPI interface)
//
//    Analog Input   # 0  - center wiper pin of 5k ohm pot (heart rate adjust)
//
//    I followed the Timer2 setup as outlined by Sebastian Wallin
//    http://popdevelop.com/2010/04/mastering-timer-interrupts-on-the-arduino/
//
//    I set up the SPI interface according to the excellent instructions of Australian John Boxall,
//    whose wonderful website has many excellent Arduino tutorials:
//    http://tronixstuff.wordpress.com/
//
//  Programmer:  James P Lynch
//               lynch007@gmail.com
//
// ***************************************************************************************************

#include "SPI.h"          // supports the SPI interface to the D/A converter and 7-segment display
#include <Wire.h>         // need the Wire library
#include <Arduino.h>
// µµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµ
#include "rgb_lcd.h"
rgb_lcd lcd;
const int colorR = 0;
const int colorG = 0;
const int colorB = 255;
// µµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµ
// various constants used by the waveform generator
#define INIT      0
#define IDLE      1
#define QRS       2
#define FOUR      4
#define THREE     3
#define TWO       2
#define ONE       1


// *******************************************************************************
//   y_data[543] - digitized ecg waveform, sampled at 1.0 msec
//
//   Waveform is scaled for a 12-bit D/A converter (0 .. 4096)
//
//   A 60 beat/min ECG would require this waveform (543 samples) plus 457 samples
//   of the first y_data[0] value of 936.
//
// *******************************************************************************
const short  y_data[] = {
  939, 940, 941, 942, 944, 945, 946, 947, 951, 956,
  962, 967, 973, 978, 983, 989, 994, 1000, 1005, 1015,
  1024, 1034, 1043, 1053, 1062, 1075, 1087, 1100, 1112, 1121,
  1126, 1131, 1136, 1141, 1146, 1151, 1156, 1164, 1172, 1179,
  1187, 1194, 1202, 1209, 1216, 1222, 1229, 1235, 1241, 1248,
  1254, 1260, 1264, 1268, 1271, 1275, 1279, 1283, 1287, 1286,
  1284, 1281, 1279, 1276, 1274, 1271, 1268, 1266, 1263, 1261,
  1258, 1256, 1253, 1251, 1246, 1242, 1237, 1232, 1227, 1222,
  1218, 1215, 1211, 1207, 1203, 1199, 1195, 1191, 1184, 1178,
  1171, 1165, 1159, 1152, 1146, 1141, 1136, 1130, 1125, 1120,
  1115, 1110, 1103, 1096, 1088, 1080, 1073, 1065, 1057, 1049,
  1040, 1030, 1021, 1012, 1004, 995, 987, 982, 978, 974,
  970, 966, 963, 959, 955, 952, 949, 945, 942, 939,
  938, 939, 940, 941, 943, 944, 945, 946, 946, 946,
  946, 946, 946, 946, 946, 947, 950, 952, 954, 956,
  958, 960, 962, 964, 965, 965, 965, 965, 965, 965,
  963, 960, 957, 954, 951, 947, 944, 941, 938, 932,
  926, 920, 913, 907, 901, 894, 885, 865, 820, 733,
  606, 555, 507, 632, 697, 752, 807, 896, 977, 1023,
  1069, 1127, 1237, 1347, 1457, 2085, 2246, 2474, 2549, 2595,
  2641, 2695, 3083, 3135, 3187, 3217, 3315, 3403, 3492, 3581,
  3804, 3847, 3890, 3798, 3443, 3453, 3297, 3053, 2819, 2810,
  2225, 2258, 1892, 1734, 1625, 998, 903, 355, 376, 203,
  30, 33, 61, 90, 119, 160, 238, 275, 292, 309,
  325, 343, 371, 399, 429, 484, 542, 602, 652, 703,
  758, 802, 838, 856, 875, 895, 917, 938, 967, 1016,
  1035, 1041, 1047, 1054, 1060, 1066, 1066, 1064, 1061, 1058,
  1056, 1053, 1051, 1048, 1046, 1043, 1041, 1038, 1035, 1033,
  1030, 1028, 1025, 1022, 1019, 1017, 1014, 1011, 1008, 1006,
  1003, 1001, 999, 998, 996, 994, 993, 991, 990, 988,
  986, 985, 983, 981, 978, 976, 973, 971, 968, 966,
  963, 963, 963, 963, 963, 963, 963, 963, 963, 963,
  963, 963, 963, 963, 963, 963, 963, 963, 963, 963,
  964, 965, 966, 967, 968, 969, 970, 971, 972, 974,
  976, 978, 980, 983, 985, 987, 989, 991, 993, 995,
  997, 999, 1002, 1006, 1011, 1015, 1019, 1023, 1028, 1032,
  1036, 1040, 1045, 1050, 1055, 1059, 1064, 1069, 1076, 1082,
  1088, 1095, 1101, 1107, 1114, 1120, 1126, 1132, 1141, 1149,
  1158, 1166, 1173, 1178, 1183, 1188, 1193, 1198, 1203, 1208,
  1214, 1221, 1227, 1233, 1240, 1246, 1250, 1254, 1259, 1263,
  1269, 1278, 1286, 1294, 1303, 1309, 1315, 1322, 1328, 1334,
  1341, 1343, 1345, 1347, 1349, 1351, 1353, 1355, 1357, 1359,
  1359, 1359, 1359, 1359, 1358, 1356, 1354, 1352, 1350, 1347,
  1345, 1343, 1341, 1339, 1336, 1334, 1332, 1329, 1327, 1324,
  1322, 1320, 1317, 1315, 1312, 1307, 1301, 1294, 1288, 1281,
  1275, 1270, 1265, 1260, 1256, 1251, 1246, 1240, 1233, 1227,
  1221, 1214, 1208, 1201, 1194, 1186, 1178, 1170, 1162, 1154,
  1148, 1144, 1140, 1136, 1131, 1127, 1123, 1118, 1114, 1107,
  1099, 1090, 1082, 1074, 1069, 1064, 1058, 1053, 1048, 1043,
  1038, 1034, 1029, 1025, 1021, 1017, 1013, 1009, 1005, 1001,
  997, 994, 990, 991, 992, 994, 996, 997, 999, 998,
  997, 996, 995, 994, 993, 991, 990, 989, 989, 989,
  989, 989, 989, 989, 988, 986, 984, 983, 981, 980,
  982, 984, 986, 988, 990, 993, 995, 997, 999, 1002,
  1005, 1008, 1012
};


// global variables used by the program
unsigned int  NumSamples = sizeof(y_data) / 2;            // number of elements in y_data[] above
unsigned int  QRSCount = 0;                               // running QRS period msec count
unsigned int  IdleCount = 0;                              // running Idle period msec count
unsigned long IdlePeriod = 0;                             // idle period is adjusted by pot to set heart rate
unsigned int  State = INIT;                               // states are INIT, QRS, and IDLE
unsigned int  DisplayCount = 0;                           // counts 50 msec to update the 7-segment display
unsigned int  tcnt2;                                      // Timer2 reload value, globally available
float         BeatsPerMinute;                             // floating point representation of the heart rate
unsigned int  Bpm;                                        // integer version of heart rate (times 10)
unsigned int  BpmLow;                                     // lowest heart rate allowed (x10)
unsigned int  BpmHigh;                                    // highest heart rate allowed (x10)
int           Value;                                      // place holder for analog input 0
unsigned long BpmValues[32] = {0, 0, 0, 0, 0, 0, 0, 0,    // holds 32 last analog pot readings
                               0, 0, 0, 0, 0, 0, 0, 0,    // for use in filtering out display jitter
                               0, 0, 0, 0, 0, 0, 0, 0,    // for use in filtering out display jitter
                               0, 0, 0, 0, 0, 0, 0, 0
                              };   // for use in filtering out display jitter
unsigned long BpmAverage = 0;                             // used in a simple averaging filter
unsigned char Index = 0;                                  // used in a simple averaging filter
unsigned int  DisplayValue = 0;                           // filtered Beats Per Minute sent to display


void setup()   {
// µµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµ
  Serial.begin(9600);
// µµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµ
  // Configure the output ports (1 msec intrerrupt indicator and D/A SPI support)
  pinMode(9, OUTPUT);                     // 7-segment display chip select   (low to select chip)
  pinMode(10, OUTPUT);                    // D/A converter chip select       (low to select chip)
  pinMode(11, OUTPUT);                    // SDI data
  pinMode(13, OUTPUT);                    // SCK clock
  
// µµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµ
lcd.begin(16, 2);
lcd.setRGB(colorR, colorG, colorB);
// µµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµ

  // initial state of SPI interface
  SPI.begin();                            // wake up the SPI bus.
  SPI.setDataMode(0);                     // mode: CPHA=0, data captured on clock's rising edge (low→high)
  SPI.setClockDivider(SPI_CLOCK_DIV64);   // system clock / 64
  SPI.setBitOrder(MSBFIRST);              // bit 7 clocks out first

  // establish the heart rate range allowed
  // BpmLow  = 300 (30 bpm x 10)
  // BpmHigh = (60.0 / (NumSamples * 0.001)) * 10  = (60.0 / .543) * 10 = 1104  (110.49 x 10)
  BpmLow = 300;
  BpmHigh = (60.0 / ((float)NumSamples * 0.001)) * 10;

  // First disable the timer overflow interrupt while we're configuring
  TIMSK2 &= ~(1 << TOIE2);

  // Configure timer2 in normal mode (pure counting, no PWM etc.)
  TCCR2A &= ~((1 << WGM21) | (1 << WGM20));
  TCCR2B &= ~(1 << WGM22);

  // Select clock source: internal I/O clock
  ASSR &= ~(1 << AS2);

  // Disable Compare Match A interrupt enable (only want overflow)
  TIMSK2 &= ~(1 << OCIE2A);

  // Now configure the prescaler to CPU clock divided by 128
  TCCR2B |= (1 << CS22)  | (1 << CS20); // Set bits
  TCCR2B &= ~(1 << CS21);           // Clear bit

  // We need to calculate a proper value to load the timer counter.
  // The following loads the value 131 into the Timer 2 counter register
  // The math behind this is:
  // (CPU frequency) / (prescaler value) = 125000 Hz = 8us.
  // (desired period) / 8us = 125.
  // MAX(uint8) + 1 - 125 = 131;
  //
  // Save value globally for later reload in ISR  /
  tcnt2 = 131;

  // Finally load end enable the timer
  TCNT2 = tcnt2;
  TIMSK2 |= (1 << TOIE2);
}


void loop() {

  // read from the heart rate pot (Analog Input 0)
  Value = analogRead(A0);

  // map the Analog Input 0 range (0 .. 1023) to the Bpm range (300 .. 1104)
  Bpm = map(Value, 0, 1023, BpmLow, BpmHigh);

  // To lessen the jitter or bounce in the display's least significant digit,
  // a moving average filter (32 values) will smooth it out.
  BpmValues[Index++] = Bpm;                       // add latest sample to eight element array
  if (Index == 32) {                              // handle wrap-around
    Index = 0;
  }
  BpmAverage = 0;
  for (int  i = 0;  i < 32; i++) {                // summation of all values in the array
    BpmAverage += BpmValues[i];
  }
  BpmAverage >>= 5;                               // Divide by 32 to get average

  // now update the 4-digit display - format: XXX.X
  // since update is a multi-byte transfer, disable interrupts until it's done
  noInterrupts();
  DisplayValue = BpmAverage;
  interrupts();

  // given the pot value (beats per minute) read in, calculate the IdlePeriod (msec)
  // this value is used by the Timer2 1.0 msec interrupt service routine
  BeatsPerMinute = (float)Bpm / 10.0;
  noInterrupts();
  IdlePeriod = (unsigned int)((float)60000.0 / BeatsPerMinute) - (float)NumSamples;
  interrupts();
  
  lcd.clear();
  lcd.setCursor(0, 0);
  lcd.print("BPM : ");
  lcd.setCursor(0, 1);
  lcd.print(DisplayValue/10);

  delay(20);
}






// ********************************************************************************
//                Timer2 Interrupt Service Routine
//
// Interrupt Service Routine (ISR) for Timer2 overflow at 1.000 msec.
//
//
// The Timer2 interrupt function is used to send the 16-bit waveform point
// to the Microchip MCP4921 D/A converter using the SPI interface.
//
// The Timer2 interrupt function is also used to send the current heart rate
// as read from the potentiometer every 50 Timer2 interrupts to the 7-segment display.
//
// The pot is read and the heart rate is calculated in the background loop.
// By running both SPI peripherals at interrupt level, we "serialize" them and avoid
// corruption by one SPI transmission being interrupted by the other.
//
// A state machime is implemented to accomplish this. It's states are:
//
//       INIT -    basically clears the counters and sets the state to QRS.
//
//       QRS  -    outputs the next ECG waveform data point every 1.0 msec
//                 there are 543 of these QRS complex data points.
//
//       IDLE -    variable period after the QRS part.
//                 D/A holds first ECG value (936) for all of the IDLE period.
//                 Idle period varies to allow adjustment of the basic heart rate;
//                 a value of zero msec for the idle period gives 110.4 beats per min
//                 while the maximum idle period of 457 msec gives 30.0 bpm.
//
//                 Note that the IDLE period is calculated in the main background
//                 loop by reading a pot and converting its range to one suitable
//                 for the background period. The interrupt routine reads this
//                 value to determine when to stop the IDLE period.
//
// The transmission of the next data point to the D/A converter via SPI takes
// about 63 microseconds (that includes two SPI byte transmissions).
//
// The transmission of the heart rate digits to the Sparkfun 7-segment display
// takes about 350 usec (it is only transmitted every 50 Timer2 interrupts)
//
// ********************************************************************************
ISR(TIMER2_OVF_vect) 
{
  // Reload the timer
  TCNT2 = tcnt2;
  // state machine
  switch (State)
  {
    case INIT:

      // zero the QRS and IDLE counters
      QRSCount = 0;
      IdleCount = 0;
      DisplayCount = 0;

      // set next state to QRS
      State = QRS;
      break;

    case QRS:

      // output the next sample in the QRS waveform to the D/A converter
      DTOA_Send(y_data[QRSCount]);

      // advance sample counter and check for end
      QRSCount++;
      if (QRSCount >= NumSamples) {
        // start IDLE period and output first sample to DTOA
        QRSCount = 0;
        DTOA_Send(y_data[0]);
        State = IDLE;
      }
      break;

    case IDLE:

      // since D/A converter will hold the previous value written, all we have
      // to do is determine how long the IDLE period should be.

      // advance idle counter and check for end
      IdleCount++;

      // the IdlePeriod is calculated in the main loop (from a pot)
      if (IdleCount >= IdlePeriod) {
        IdleCount = 0;
        State = QRS;
      }
      break;

    default:
      break;
  }

/*
  // output to the 7-segment display every 50 msec
  DisplayCount++;
  if (DisplayCount >= 50) {
    DisplayCount = 0;
      Display7Seg_Send(DisplayValue);
  }
  */

}




// ***************************************************************************************************
//                      void  DTOA_Send(unsigned short)
//
//  Purpose: send 12-bit D/A value to Microchip MCP4921 D/A converter ( 0 .. 4096 )
//
//
//  Input:    DtoAValue - 12-bit D/A value ( 0 .. 4096 )
//
//
//            The DtoAValue is prepended with the A/B, BUF, GA, and SHDN bits before transmission.
//
//                                           WRITE COMMAND
//            |--------|--------|--------|--------|--------------------------------------------------|
//            |  A/B   |   BUF  |  GA    | SHDN   | D11 D10 D09 D08 D07 D06 D05 D04 D03 D02 D01 D00  |
//            |        |        |        |        |                                                  |
//            |setting:|setting:|setting:|Setting:|            DtoAValue   (12 bits)                 |
//            |   0    |   0    |   1    |   1    |                                                  |
//            | DAC-A  |unbuffer|   1x   |power-on| ( 0 .. 4096  will output as 0 volts .. 5 volts ) |
//            |--------|--------|--------|--------|--------------------------------------------------|
//                15      14        13       12    11                                               0
//  To D/A    <======================================================================================
//
//            Note:  WriteCommand is clocked out with bit 15 first!
//
//
//  Returns:  nothing
//
//
//  I/O Resources:  Digital Pin 9  = chip select (low to select chip)
//                  Digital Pin 13 = SPI Clock
//                  Digital Pin 11 = SPI Data
//
//  Note: by grounding the LDAC* pin in the hardware hook-up, the SPI data will be clocked into the
//        D/A converter latches when the chip select rises at the end-of-transfer.
//
//        This routine takes 63 µ sec using an Adafruit Menta
// ***************************************************************************************************
void  DTOA_Send(unsigned short DtoAValue) {

  byte            Data = 0;
  // select the D/A chip (low)
  digitalWrite(10, 0);    // chip select low

  // send the high byte first 0011xxxx
  Data = highByte(DtoAValue);
  Data = 0b00001111 & Data;
  Data = 0b00110000 | Data;
  SPI.transfer(Data);

  // send the low byte next xxxxxxxx
  Data = lowByte(DtoAValue);
  SPI.transfer(Data);

  // all done, de-select the chip (this updates the D/A with the new value)
  digitalWrite(10, 1);    // chip select high
}





// ***************************************************************************************************
//                      void  Display7Seg_Send(char *)
//
//  Purpose: send 4 digits to SparkFun Serial 7-Segment Display (requires 4 SPI writes)
//
//  Input:    value -  unsigned int version of BeatsPerMinute
//
//  Returns:  nothing
//
//  I/O Resources:  Digital Pin 10 = chip select (low to select chip)
//                  Digital Pin 13 = SPI Clock
//                  Digital Pin 11 = SPI Data
//
//  Note:  this routine takes 350 usec using an Adafruit Menta
// ***************************************************************************************************
/* 
void  Display7Seg_Send(unsigned int  HeartRate) {
  
// µµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµ
//  Serial.println(DisplayValue);
//  lcd.setCursor(0, 0);
//  lcd.print(DisplayValue);
// µµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµ


  uint8_t       digit1, digit2, digit3, digit4;
  unsigned int  value;

  // convert to four digits (set leading zeros to blanks; 0x78 is the blank character)
  value = HeartRate;
  digit1 = value / 1000;
  value -= digit1 * 1000;
  if (digit1 == 0) digit1 = 0x78;

  digit2 = value / 100;
  value -= digit2 * 100;
  if ((digit1 == 0x78) && (digit2 == 0)) digit2 = 0x78;

  digit3 = value / 10;
  value -= digit3 * 10;
  if ((digit1 == 0x78) && (digit2 == 0x78) && (digit3 == 0)) digit3 = 0x78;

  digit4 = value;

  digitalWrite(9, LOW);    // select the Sparkfun 7-seg display
  SPI.transfer(0x76);      // reset display
  SPI.transfer(0x7A);      // brightness command
  SPI.transfer(0x00);      // 0 = bright,  255 = dim
  Serial.print('\t');
  Serial.print(digit1); Serial.print('\t');
  SPI.transfer(digit1);    // Thousands Digit
  Serial.print(digit2); Serial.print('\t');
  SPI.transfer(digit2);    // Hundreds Digit
  Serial.print(digit3); Serial.print('\t');
  SPI.transfer(digit3);    // Tens Digit
  Serial.print(digit4); Serial.println("");
  SPI.transfer(digit4);    // Ones Digit
  SPI.transfer(0x77);      // set decimal points command
  SPI.transfer(0x04);      // turn on dec pt between digits 3 and 4
  digitalWrite(9, HIGH);   // release Sparkfun 7-seg display
}
*/

Je pense qu'il y a clairement moyen de simplifier tout ça.

hello
je joue avec ton code, et je constate que la trace est affichée en un temps constant ( période QRS) et qu'ensuite, on fait un trait sur zéro en attendant la fin de la période de la fréquence cardiaque.
est ce normal.
je pensais que la trace se dessinait sur toute la période de la fréquence cardiaque, sans trait sur le zéro.
autrement dit, le spot utilise toute la largeur de l'écran puis recommence le pulse suivant. le système s'arrangeant pour synchroniser la fin de la fréquence cardiaque et la droite de l'écran.
peux tu me renseigner?

.

finalement, je pense que je raisonne coté récepteur alors que ton programme est vu coté émetteur