Différences entre versions de « P2E-THEREMIN »
(→Code) |
|||
| Ligne 180 : | Ligne 180 : | ||
Le script [https://github.com/mchobby/pico-2-explorer/blob/main/projects/theremin/theremin.py theremin.py] est disponible dans le [https://github.com/mchobby/pico-2-explorer dépôt dédié au Pico-2-Explorer]. | Le script [https://github.com/mchobby/pico-2-explorer/blob/main/projects/theremin/theremin.py theremin.py] est disponible dans le [https://github.com/mchobby/pico-2-explorer dépôt dédié au Pico-2-Explorer]. | ||
| + | |||
| + | Cette version du Théremin divise l'espace des 30cm au dessus de la photo résistance en une progression linéaire de la fréquence entre 523 Hz (DO4) à 1046 Hz (DO5). | ||
<syntaxhighlight lang="python" line> | <syntaxhighlight lang="python" line> | ||
| + | from machine import Pin, ADC, PWM | ||
| + | from math import exp | ||
| + | from maps import map | ||
import micropython, time | import micropython, time | ||
| − | + | micropython.alloc_emergency_exception_buf(100) | |
| + | |||
| + | C = 79.6778221 | ||
| + | D = 499 | ||
| + | |||
| + | DO4 = 523 | ||
| + | DO5 = 1046 | ||
| + | |||
| + | Pin( 23, Pin.OUT, value=True ) | ||
| + | |||
| + | counter = 0 | ||
| + | def bouton_cb( obj ): | ||
| + | global counter | ||
| + | counter += 1 | ||
| + | |||
| + | btn = Pin( Pin.board.GP16, Pin.IN, Pin.PULL_UP ) | ||
| + | btn.irq( handler=bouton_cb, trigger=Pin.IRQ_FALLING ) | ||
| + | |||
| + | buzzer = PWM( Pin( Pin.board.GP13 ) ) | ||
| + | buzzer.freq( 500 ) | ||
| + | buzzer.duty_u16( 0 ) # silence | ||
| + | |||
| + | a1 = ADC( Pin( Pin.board.GP27 ) ) | ||
| + | while True: | ||
| + | if counter>0: | ||
| + | if buzzer.duty_u16() == 0: | ||
| + | buzzer.duty_u16( 65535//3 ) | ||
| + | else: | ||
| + | buzzer.duty_u16( 0 ) | ||
| + | time.sleep_ms(300) | ||
| + | counter=0 | ||
| + | continue | ||
| + | # lecture analogique sur 10 bits | ||
| + | val = 0 | ||
| + | for i in range( 10 ): | ||
| + | val += (a1.read_u16()>>6) | ||
| + | val = val/10 # Moyenne | ||
| + | |||
| + | cm = exp( (val-D)/C ) | ||
| + | |||
| + | freq = int( map( cm, 1, 30, DO4, DO5 ) ) | ||
| + | buzzer.freq( freq ) | ||
| + | print( 'adc_10bits=%5i , cm=%2.2f, freq=%i Hz' % (val,cm,freq) ) | ||
| + | time.sleep_ms( 20 ) | ||
</syntaxhighlight> | </syntaxhighlight> | ||
| − | |||
== Ressources == | == Ressources == | ||
Version du 9 avril 2025 à 20:34
Theremin
L'idée
A propos de la Photo-résistance
Une même surface masquant la photorésistance, il est instinctivement possible de se rendre compte que luminosité captée va croître/décroître avec le carré de la distance (puisque l'ombre se propage dans les deux directions X et Y.
Cette relation au carré de la distance, même si pas encore démontrée, existe aussi pour la puissance sonore (qui diminue avec le carré de la distance).
C'est pour cette raison que les potentiomètres de réglage de volumes n'ont pas de fonction linéaire mais une fonction logarithmique. Cela donne l' impression à l'oreille d'avoir un réglage du volume qui augmente linéairement avec la rotation du potentiomètre.
Linéaire ou pas, quel importance?
Le but du Theremin est de faire varier le son de façon proportionnelle avec la position de la main au dessus de la photorésistance.
Il est donc nécessaire d'avoir une relation linéaire entre la position de la main et la fréquence du son produit.
Savoir si la relation lumière/résistance de la photo-résistance est linéaire ou pas a donc de toute importance!
Si, comme supposé, la relation lumière/résistance est logarithmique cela signifie que la majorité de la variation de résistance (donc de fréquence du son) s'opère a proximité de la photo-résistance. Dans pareil cas, il faudra faire en sorti de reconvertir la réponse logarithmique vers une réponse linéaire.
Mesures sur la photo-résistance
| Ce point et le suivant ne sont utiles que si vous avez besoin de calculer vos propres valeurs de C et D. Ce qui pourrait être utile étant donné que toutes les photo-résistances ont des caractéristiques différentes. |
Il est très facile de se faire une idée de la réponse lumière/résistance en utilisant des conditions d'illumination constante et en pratiquant une série de mesure en positionnant la main à une hauteur donnée puis en mesurant la réponse sur le convertisseur ADC.
En utilisant le câblage du Theremin, le petit script read.py (présent dans le projet Theremin) permet d'effectuer les relevés nécessaires "sans main" (la valeur 99999) puis à 30cm, 25cm, 20cm, 15cm, 10cm, 5cm et enfin 1cm simplement en pressant le bouton lorsque la main est positionnée.
Les valeurs obtenues sont encodées sur 10 bits significatifs (donc de 0 à 1023) pour rester au dessus du bruit (voir tutoriel sur l'entrée analogique).
import micropython, time
from machine import Pin, ADC
micropython.alloc_emergency_exception_buf(100)
counter = 0
def bouton_cb( obj ):
global counter
counter += 1
# https://docs.micropython.org/en/latest/library/machine.Pin.html
# https://docs.micropython.org/en/latest/reference/isr_rules.html
btn = Pin( Pin.board.GP16, Pin.IN, Pin.PULL_UP )
btn.irq( handler=bouton_cb, trigger=Pin.IRQ_FALLING )
a1 = ADC( Pin( Pin.board.GP27 ) )
read_for_cm = [99999,30,25,20,15,10,5,1]
index = 0
print( "read for %i cm" % read_for_cm[index] )
while True:
if counter>0: # Button pressed?
val = 0
# Acquire analog on 10 bits
val = 0
for i in range( 10 ):
val += (a1.read_u16()>>6)
# calculer la moyenne
val = val/10
print( " %i of 1024" % vald )
index += 1
if index>=len( read_for_cm ):
index = 0
# next value
print( "read for %i cm" % read_for_cm[index] )
# Reset button detection
time.sleep_ms( 500 )
counter=0
else:
time.sleep_ms( 200 )
Ce qui permet de faire un relevé comme celui-ci dessous.
read for 99999 cm
793 of 1024
read for 30 cm
770 of 1024
read for 25 cm
758 of 1024
read for 20 cm
755 of 1024
read for 15 cm
742 of 1024
read for 10 cm
708 of 1024
read for 5 cm
634 of 1024
read for 1 cm
499 of 1024
A noter également le minima de 500 et maxima de 793.
En reportant les valeurs dans un tableur, il est clairement possible de voir la progression logarithmique avec le plafonnement vers le maxima.
remarque: la distance 99999 est ici remplacée par 50 pour faciliter le rendu du graphique.
Non seulement cela ressemble à une progression logarithmique mais il serait aussi possible de faire coller un logarithme népérien (fonction ln().
La fonction s'exprime, en première approximation, sous la forme:
y = ln(x)*C+D
avec:
- D = valeur à 1cm car x=1 => ln(1)=0 .
- C = (valeur_à_30cm - D) / ln(30) .
Avec les relevés ci-avant, cela donne:
- D = 499
- C = (770-499)/ln(30) = 79.6778221
La fonction d'approximation s'exprimerait donc:
y = ln(x) * 79.6778221 + 499
La graphique ci-dessous reprend la fonction g(x)=ln(x)*79.6778221+499 (en rouge) et la compare avec les données expérimentales (en bleu).
Ce n'est pas une parfaite correspondance mais c'est suffisamment proche pour passer au point suivant.
Fonction réciproque
Maintenant que nous avons identifié la fonction y = f(x) sous la forme:
y = ln(x) * C + D y = ln(x) * 79.6778221 + 499
Avec:
- y la valeur du convertisseur ADC (sur 10 bits)
- x la position de la main au dessus du capteur en cm.
Ce qui serait intéressant, c'est de pouvoir calculer la fonction réciproque x = f-1(y) .
La hauteur de la main étant linéaire, la fonction réciproque permettra aussi produire une variation linéaire de la fréquence du son avec la hauteur de la main.
La réciproque de ln() est la fonction exp() .
x = exp( (y-D)/C ) x = exp( (y-499)/79.6778221 )
Par exemple, si le convertisseur analogique (ADC) retourne la valeur 10bits de 680 (y=680), la main est située à 9.69 cm comme l'indique le calcul ci-dessous:
x = exp( (680-499)/79.6778221 ) x = 9.69 cm
Brancher
| Pico | Pico-2-Explorer |
| GP16 | Btn3 |
| GP27 | Photo-résistance |
| GP13 | Buzzer (cavalier place) |
Le bouton permet d'activer/désactiver la génération du signal sonore. Ne pas oublier de presser le bouton après avoir démarré le script.
Code
Le script theremin.py est disponible dans le dépôt dédié au Pico-2-Explorer.
Cette version du Théremin divise l'espace des 30cm au dessus de la photo résistance en une progression linéaire de la fréquence entre 523 Hz (DO4) à 1046 Hz (DO5).
1 from machine import Pin, ADC, PWM
2 from math import exp
3 from maps import map
4 import micropython, time
5 micropython.alloc_emergency_exception_buf(100)
6
7 C = 79.6778221
8 D = 499
9
10 DO4 = 523
11 DO5 = 1046
12
13 Pin( 23, Pin.OUT, value=True )
14
15 counter = 0
16 def bouton_cb( obj ):
17 global counter
18 counter += 1
19
20 btn = Pin( Pin.board.GP16, Pin.IN, Pin.PULL_UP )
21 btn.irq( handler=bouton_cb, trigger=Pin.IRQ_FALLING )
22
23 buzzer = PWM( Pin( Pin.board.GP13 ) )
24 buzzer.freq( 500 )
25 buzzer.duty_u16( 0 ) # silence
26
27 a1 = ADC( Pin( Pin.board.GP27 ) )
28 while True:
29 if counter>0:
30 if buzzer.duty_u16() == 0:
31 buzzer.duty_u16( 65535//3 )
32 else:
33 buzzer.duty_u16( 0 )
34 time.sleep_ms(300)
35 counter=0
36 continue
37 # lecture analogique sur 10 bits
38 val = 0
39 for i in range( 10 ):
40 val += (a1.read_u16()>>6)
41 val = val/10 # Moyenne
42
43 cm = exp( (val-D)/C )
44
45 freq = int( map( cm, 1, 30, DO4, DO5 ) )
46 buzzer.freq( freq )
47 print( 'adc_10bits=%5i , cm=%2.2f, freq=%i Hz' % (val,cm,freq) )
48 time.sleep_ms( 20 )
Ressources
- Machine.Pin : cette ressource de MicroPython reprend, entre autre, la documentation sur les interruption et les broches.
- Développement avec interruption : cette ressource de MicroPython reprend de nombreuses recommandations concernant le développement de routines d'interruption avec MicroPython.
Traduction augmentée réalisée par Meurisse. D pour shop.MCHobby.be - Licence CC-BY-SA.
