123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233 |
- """
- Library für die Nutzung des MAX4466 Mikrofons als digitales Stethoskop
- Author: Adrian Böschel
- letzte Änderung: 18.04.2024
- """
- import uos
- from machine import Pin, Timer, ADC, SPI
- from libs.SDCard import SDCard
- class DigitalStethoskop:
- """Klasse zur Nutzung des Raspberry Pi Pico mit dem MAX4466 Sensor als Digitales Stethoskop
- Raises:
- ValueError: bei Änderung eines Wertes der nicht verändert werden darf
- Returns:
- DigitalStethoskop: Objekt dieser Klasse
- """
-
- ############################## INITIALISIERUNG ##############################
- def __init__(self, sample_rate:int, signal_pin:Pin = Pin(26), file='data.txt'):
- """Initialisiert ein neues DigitalStethoskop Objekt
- Args:
- SAMPLE_RATE (int): Abtastrate mit der die Signale aufgezeichnet werden.
- SIGNAL_PIN (Pin, optional): Pin an dem Signal des MAX4466 angeschlossen ist. Muss ADC-fähig sein. Defaults to Pin(26).
- file (str, optional): Standard Name der Datei unter der die Daten gespeichert werden können. Defaults to 'data.txt'.
- """
- # Variables
- self.sample_rate = sample_rate
- self.count = 0
- self.MAX_DATA = 8000
-
- # Pins
- self.__SIGNAL_PIN = self.__check_Pin(signal_pin)
- # ADC
- self.__adc = ADC(self.__SIGNAL_PIN) # 0 - 65535
- self.__deltaU = 3.3/65536 # voltage step per adc value
- # TIMER
- self.__timer = Timer() # verwende Hardware Timer 0
-
- self.__file = self.__check_path(file)
- self.d = []
- self.has_sd = False
- try:
- self.sd = SDCard(SPI(1,sck=Pin(10),mosi=Pin(11),miso=Pin(12)), Pin(15))
- self.vfs = uos.VfsFat(self.sd)
- uos.mount(self.vfs, "/sd") # Mount filesystem
- self.has_sd = True
- except OSError:
- print("No SD Card detected.")
-
- ############################## PROPERTIES ##############################
- # SIGNAL PIN
- @property
- def SIGNAL_PIN(self):
- return self.__SIGNAL_PIN
-
- @SIGNAL_PIN.setter
- def SIGNAL_PIN(self, pin):
- self.__SIGNAL_PIN = self.__check_Pin(pin) # überprüft, ob der Pin geeignet ist bei Änderung der Variable
- # Analog-Digital-Converter
- @property
- def adc(self):
- return self.__adc
-
- @adc.setter
- def adc(self, val):
- raise ValueError("ADC Value is automated and shall not be changed.")
-
- # max length of data to be recorded
- @property
- def MAX_DATA(self):
- return self.__MAX_DATA
-
- @MAX_DATA.setter
- def MAX_DATA(self, val):
- self.__MAX_DATA = val
- print(f"Maximum amount of Data to be stored: {self.__MAX_DATA}")
- ############################## CHECKS ##############################
- def __check_Pin(self, SIG_PIN:Pin):
- """Check if the Pin is capable of using the ADC of the Pico\n
- This only applies for the GPIOs 26-29.
- Args:
- SIG_PIN (Pin): Pin to get checked
- Returns:
- Pin: returns the Pin if its correct. Otherwise it defaults to Pin(26)
- """
- if (SIG_PIN == Pin(26)) or (SIG_PIN == Pin(27)) or (SIG_PIN == Pin(28)) or (SIG_PIN == Pin(29)):
- return SIG_PIN
- else:
- print("Choose one of the ADC-Pins of RaspberryPi Pico (GP26, GP27, GP28 or GP29)!\nDefaulting to Pin 26!")
- return Pin(26)
-
- def __check_path(self, path):
- """if path is None the Value prespecified within the class will be used. otherwise it checks if the filename is formatted right
- Args:
- path (str): String to be checked, either \".txt\" or \".csv\" (eg: "data.txt")
- Returns:
- None (None): for a wrong input.
- path (str): if the file name has been declared right
- """
- if path == None:
- return self.__file
- else:
- # check if the file format has been written right
- c = path.split(".")
- try:
- if c[1] == "txt":
- pass
- elif c[1] == "csv":
- pass
- else:
- print("wrong file format! You can save as \".txt\" or \".csv\"")
- return None
- except:
- print("wrong string format! Check the spelling")
- return None
- return path
- ############################## ISRS ##############################
- def __isr(self, timer):
- """Interrupt Service Routine, liest den Wert des ADC aus, wandelt ihn vom Bitwert in Spannungswert und speichert ihn im RAM ab, erhöht den Counter der aufgenommenen Werte
- Args:
- timer (Timer): Für callbacks die von Timer Objekten aufgerufen werden muss immer die timer variable mit übergeben werden
- """
- val = self.__adc.read_u16() * self.__deltaU
- self.d.append(val)
- self.count += 1
- ############################## METHODEN ##############################
- def record_data(self, time_seconds):
- """starts recording data for the specified duration as long as the amount of points to be recorded is below the MAX_DATA.\n
- Points can only get saved in the RAM therefore the maximum amount is limited\n
- points = time_seconds * sample_rate\n
-
- Args:
- time_seconds (int/float): time in seconds to record data
- """
- points = round(time_seconds * self.sample_rate) # calculate amount of points to be recorded
-
- if points > self.MAX_DATA:
- print("Can not start recording data!\nThe amount of data points to be recorded is higher than the available space in the RAM!\nChoose lower length of recording data or a lower sample rate!")
- return
-
- print(f"Start recording Data for {time_seconds} seconds with {self.sample_rate} Hz!")
-
- self.__timer.init(mode=Timer.PERIODIC, freq=self.sample_rate, callback=self.__isr) # start recording
- while self.count < points:
- pass
- self.__timer.deinit()
- print("End of recording Data!")
- def reset(self):
- """can be called after record_data to ensure it works a second time without restarting the Pico\n
- Stops recording and deletes all available data in the RAM of the Pico! Be sure to save before if needed.\n
- """
- self.__timer.deinit()
- self.count = 0
- self.d.clear()
- def save(self, file=None):
- """Saves the available Data to the internal Flash Memory of the Pico under the specified file name\n
- Args:
- file (str, optional): The file can either be a \".txt\" or \".csv\" file. Defaults to None which will save to the prespecified filename.
- """
- p = self.__check_path(file)
- if p == None:
- return
- if len(self.d) == 0:
- print("No Data recorded yet!")
- return
-
- try:
- self.f = open(p, 'w')
- print(f"writing to \"{p}\"")
- self.f.write(f"Data recorded with Sample Rate: {self.sample_rate}\n")
- for element in self.d:
- self.f.write(f"{element}\n")
- self.f.close()
- except:
- print("Error with opening the file.")
- def save_sd(self, file=None):
- """Saves the available Data to the SD Card inserted into the Maker Pi Pico Board under the specified file name\n
- Args:
- file (str, optional): The file can either be a \".txt\" or \".csv\" file. Defaults to None which will save to the prespecified filename.
- """
- p = self.__check_path(file)
- if p == None:
- return
- if self.has_sd == False:
- print("SD Card has not been initialized right!")
- return
- if len(self.d) == 0:
- print("No Data recorded yet!")
- return
-
- try:
- # setup the DATA FILE
- self.f = open(f"/sd/{self.__file}", "w")
- print(f"writing to \"/sd/{self.__file}\"")
- self.f.write(f"Data recorded with Sample Rate: {self.sample_rate}\n") # create Header
- for element in self.d:
- self.f.write(f"{element}\n")
- self.f.close()
- except:
- print("Error with opening the file on the sd card")
- def get_data(self):
- """ gibt das Array der aufgezeichneten Daten zurück
- sollten keine Werte aufgenommen worden sein wird 0 returned
- """
- if len(self.d) > 0:
- return self.d
- else:
- print("Noch keine Daten aufgezeichnet!")
- return 0
|