""" 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