__init__.py 8.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233
  1. """
  2. Library für die Nutzung des MAX4466 Mikrofons als digitales Stethoskop
  3. Author: Adrian Böschel
  4. letzte Änderung: 18.04.2024
  5. """
  6. import uos
  7. from machine import Pin, Timer, ADC, SPI
  8. from libs.SDCard import SDCard
  9. class DigitalStethoskop:
  10. """Klasse zur Nutzung des Raspberry Pi Pico mit dem MAX4466 Sensor als Digitales Stethoskop
  11. Raises:
  12. ValueError: bei Änderung eines Wertes der nicht verändert werden darf
  13. Returns:
  14. DigitalStethoskop: Objekt dieser Klasse
  15. """
  16. ############################## INITIALISIERUNG ##############################
  17. def __init__(self, sample_rate:int, signal_pin:Pin = Pin(26), file='data.txt'):
  18. """Initialisiert ein neues DigitalStethoskop Objekt
  19. Args:
  20. SAMPLE_RATE (int): Abtastrate mit der die Signale aufgezeichnet werden.
  21. SIGNAL_PIN (Pin, optional): Pin an dem Signal des MAX4466 angeschlossen ist. Muss ADC-fähig sein. Defaults to Pin(26).
  22. file (str, optional): Standard Name der Datei unter der die Daten gespeichert werden können. Defaults to 'data.txt'.
  23. """
  24. # Variables
  25. self.sample_rate = sample_rate
  26. self.count = 0
  27. self.MAX_DATA = 8000
  28. # Pins
  29. self.__SIGNAL_PIN = self.__check_Pin(signal_pin)
  30. # ADC
  31. self.__adc = ADC(self.__SIGNAL_PIN) # 0 - 65535
  32. self.__deltaU = 3.3/65536 # voltage step per adc value
  33. # TIMER
  34. self.__timer = Timer() # verwende Hardware Timer 0
  35. self.__file = self.__check_path(file)
  36. self.d = []
  37. self.has_sd = False
  38. try:
  39. self.sd = SDCard(SPI(1,sck=Pin(10),mosi=Pin(11),miso=Pin(12)), Pin(15))
  40. self.vfs = uos.VfsFat(self.sd)
  41. uos.mount(self.vfs, "/sd") # Mount filesystem
  42. self.has_sd = True
  43. except OSError:
  44. print("No SD Card detected.")
  45. ############################## PROPERTIES ##############################
  46. # SIGNAL PIN
  47. @property
  48. def SIGNAL_PIN(self):
  49. return self.__SIGNAL_PIN
  50. @SIGNAL_PIN.setter
  51. def SIGNAL_PIN(self, pin):
  52. self.__SIGNAL_PIN = self.__check_Pin(pin) # überprüft, ob der Pin geeignet ist bei Änderung der Variable
  53. # Analog-Digital-Converter
  54. @property
  55. def adc(self):
  56. return self.__adc
  57. @adc.setter
  58. def adc(self, val):
  59. raise ValueError("ADC Value is automated and shall not be changed.")
  60. # max length of data to be recorded
  61. @property
  62. def MAX_DATA(self):
  63. return self.__MAX_DATA
  64. @MAX_DATA.setter
  65. def MAX_DATA(self, val):
  66. self.__MAX_DATA = val
  67. print(f"Maximum amount of Data to be stored: {self.__MAX_DATA}")
  68. ############################## CHECKS ##############################
  69. def __check_Pin(self, SIG_PIN:Pin):
  70. """Check if the Pin is capable of using the ADC of the Pico\n
  71. This only applies for the GPIOs 26-29.
  72. Args:
  73. SIG_PIN (Pin): Pin to get checked
  74. Returns:
  75. Pin: returns the Pin if its correct. Otherwise it defaults to Pin(26)
  76. """
  77. if (SIG_PIN == Pin(26)) or (SIG_PIN == Pin(27)) or (SIG_PIN == Pin(28)) or (SIG_PIN == Pin(29)):
  78. return SIG_PIN
  79. else:
  80. print("Choose one of the ADC-Pins of RaspberryPi Pico (GP26, GP27, GP28 or GP29)!\nDefaulting to Pin 26!")
  81. return Pin(26)
  82. def __check_path(self, path):
  83. """if path is None the Value prespecified within the class will be used. otherwise it checks if the filename is formatted right
  84. Args:
  85. path (str): String to be checked, either \".txt\" or \".csv\" (eg: "data.txt")
  86. Returns:
  87. None (None): for a wrong input.
  88. path (str): if the file name has been declared right
  89. """
  90. if path == None:
  91. return self.__file
  92. else:
  93. # check if the file format has been written right
  94. c = path.split(".")
  95. try:
  96. if c[1] == "txt":
  97. pass
  98. elif c[1] == "csv":
  99. pass
  100. else:
  101. print("wrong file format! You can save as \".txt\" or \".csv\"")
  102. return None
  103. except:
  104. print("wrong string format! Check the spelling")
  105. return None
  106. return path
  107. ############################## ISRS ##############################
  108. def __isr(self, timer):
  109. """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
  110. Args:
  111. timer (Timer): Für callbacks die von Timer Objekten aufgerufen werden muss immer die timer variable mit übergeben werden
  112. """
  113. val = self.__adc.read_u16() * self.__deltaU
  114. self.d.append(val)
  115. self.count += 1
  116. ############################## METHODEN ##############################
  117. def record_data(self, time_seconds):
  118. """starts recording data for the specified duration as long as the amount of points to be recorded is below the MAX_DATA.\n
  119. Points can only get saved in the RAM therefore the maximum amount is limited\n
  120. points = time_seconds * sample_rate\n
  121. Args:
  122. time_seconds (int/float): time in seconds to record data
  123. """
  124. points = round(time_seconds * self.sample_rate) # calculate amount of points to be recorded
  125. if points > self.MAX_DATA:
  126. 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!")
  127. return
  128. print(f"Start recording Data for {time_seconds} seconds with {self.sample_rate} Hz!")
  129. self.__timer.init(mode=Timer.PERIODIC, freq=self.sample_rate, callback=self.__isr) # start recording
  130. while self.count < points:
  131. pass
  132. self.__timer.deinit()
  133. print("End of recording Data!")
  134. def reset(self):
  135. """can be called after record_data to ensure it works a second time without restarting the Pico\n
  136. Stops recording and deletes all available data in the RAM of the Pico! Be sure to save before if needed.\n
  137. """
  138. self.__timer.deinit()
  139. self.count = 0
  140. self.d.clear()
  141. def save(self, file=None):
  142. """Saves the available Data to the internal Flash Memory of the Pico under the specified file name\n
  143. Args:
  144. file (str, optional): The file can either be a \".txt\" or \".csv\" file. Defaults to None which will save to the prespecified filename.
  145. """
  146. p = self.__check_path(file)
  147. if p == None:
  148. return
  149. if len(self.d) == 0:
  150. print("No Data recorded yet!")
  151. return
  152. try:
  153. self.f = open(p, 'w')
  154. print(f"writing to \"{p}\"")
  155. self.f.write(f"Data recorded with Sample Rate: {self.sample_rate}\n")
  156. for element in self.d:
  157. self.f.write(f"{element}\n")
  158. self.f.close()
  159. except:
  160. print("Error with opening the file.")
  161. def save_sd(self, file=None):
  162. """Saves the available Data to the SD Card inserted into the Maker Pi Pico Board under the specified file name\n
  163. Args:
  164. file (str, optional): The file can either be a \".txt\" or \".csv\" file. Defaults to None which will save to the prespecified filename.
  165. """
  166. p = self.__check_path(file)
  167. if p == None:
  168. return
  169. if self.has_sd == False:
  170. print("SD Card has not been initialized right!")
  171. return
  172. if len(self.d) == 0:
  173. print("No Data recorded yet!")
  174. return
  175. try:
  176. # setup the DATA FILE
  177. self.f = open(f"/sd/{self.__file}", "w")
  178. print(f"writing to \"/sd/{self.__file}\"")
  179. self.f.write(f"Data recorded with Sample Rate: {self.sample_rate}\n") # create Header
  180. for element in self.d:
  181. self.f.write(f"{element}\n")
  182. self.f.close()
  183. except:
  184. print("Error with opening the file on the sd card")
  185. def get_data(self):
  186. """ gibt das Array der aufgezeichneten Daten zurück
  187. sollten keine Werte aufgenommen worden sein wird 0 returned
  188. """
  189. if len(self.d) > 0:
  190. return self.d
  191. else:
  192. print("Noch keine Daten aufgezeichnet!")
  193. return 0