diff --git a/README.md b/README.md index 2ff2411..ae312c6 100644 --- a/README.md +++ b/README.md @@ -7,5 +7,19 @@ SW3 = Pin(("GPIO_0", 4), Pin.IN) SW3.irq(lambda t: print("SW3 changed")) ``` - [Using flash memory as non-volatile storage on the Pi Pico microcontroller](https://kevinboone.me/picoflash.html?i=2) - - https://pypi.org/project/littlefs-python/ - - [buzzer reference](https://www.tomshardware.com/how-to/buzzer-music-raspberry-pi-pico) \ No newline at end of file + - https://pypi.org/project/littlefs-python/ + - [buzzer reference](https://www.tomshardware.com/how-to/buzzer-music-raspberry-pi-pico) + - [esp32 EEPROM read/write cycle](https://stackoverflow.com/questions/59817278/esp32-eeprom-read-write-cycle) stack overflow article + \ Summary: \ + - ESP32 doesn’t have an actual EEPROM; instead it uses some of its flash storage to mimic an EEPROM + - ESP32 flash goes close to 10,000 cycles + - even automotive grade EEPROM like the 24LC001 which supports at least 1,000,000 writes will only last about 2 months + - EERAM which supports infinite writes and will not loose contents on power loss (microchip 47C04) + - [upy eeprom t24Cxx family driver lib](https://github.com/dda/MicroPython/blob/master/EEPROM.py) + - [viu for R$2,90](https://www.autocorerobotica.com.br/at24c02-ci-memoria-eeprom) + - sd card module + - R$4 in [ali](https://pt.aliexpress.com/item/1005003664036461.html) + - R$30 in [mercado livre](https://produto.mercadolivre.com.br/MLB-2725721573-modulo-para-carto-micro-sd-automaco-leitor-gravador-spi-_JM) + + + \ No newline at end of file diff --git a/src/alarm.py b/src/alarm.py new file mode 100644 index 0000000..3f7166f --- /dev/null +++ b/src/alarm.py @@ -0,0 +1,37 @@ +import time +from machine import Pin +import buzzer +from display import Display +import constants + + +SATURATED_MESSAGE = """Filtro saturado.""" +HOLE_MESSAGE = """Filtro ou linha \nfurado.""" + +class Alarm: + reset_btnn = Pin(constants.RESET_BUTTON_PIN, mode=Pin.IN) + @staticmethod + def trigger_saturated_alarm(): + Display.print(SATURATED_MESSAGE) + buzzer.sing_alarm() + while Alarm.reset_button_not_pressed(): + time.sleep(0.05) + Alarm.stop_alarm() + + @staticmethod + def trigger_hole_alarm(): + Display.print(HOLE_MESSAGE) + buzzer.sing_alarm() + while Alarm.reset_button_not_pressed(): + time.sleep(0.05) + Alarm.stop_alarm() + + @staticmethod + def stop_alarm(): + Display.clear() + buzzer.stop() + + @staticmethod + def reset_button_not_pressed(): + was_not_pressed = Alarm.reset_btnn.value() < 1 + return was_not_pressed diff --git a/src/boot.py b/src/boot.py new file mode 100644 index 0000000..b8e7bb0 --- /dev/null +++ b/src/boot.py @@ -0,0 +1 @@ +# boot.py -- run on boot-up diff --git a/src/buzzer.py b/src/buzzer.py new file mode 100644 index 0000000..020e9be --- /dev/null +++ b/src/buzzer.py @@ -0,0 +1,22 @@ +from machine import Pin, PWM +import constants +import parameters + + +buzzer = PWM(Pin(constants.BUZZER_PIN)) +buzzer.duty_u16(0) + +def sing_alarm(): + if parameters.ANNOYING_BUZZER_ENABLED: + tone = constants.ANNOYTING_BUZZER_TONE + else: + tone = constants.NORMAL_BUZZER_TONE + buzzer.duty_u16(tone) + + +def stop(): + buzzer.duty_u16(0) + + +def sing_tone(tone): + buzzer.duty_u16(tone) diff --git a/src/constants.py b/src/constants.py new file mode 100644 index 0000000..a9059d6 --- /dev/null +++ b/src/constants.py @@ -0,0 +1,15 @@ +"""RASPBERRY PICO PINS""" +"""PINOUTS""" +PRESSURE_SENSOR_PIN = 26 +HALL_SENSOR_PIN = 27 +RESET_BUTTON_PIN = 22 +BUZZER_PIN = 15 +LCD_SDA = 0 +LCD_SCL = 1 + + +"""FIXED NUMBERS""" +ANNOYTING_BUZZER_TONE = 51250 +NORMAL_BUZZER_TONE = 200 +ADC_RESOLUTION = 65535.0 + diff --git a/src/display.py b/src/display.py new file mode 100644 index 0000000..0b7a60f --- /dev/null +++ b/src/display.py @@ -0,0 +1,32 @@ +from machine import Pin, I2C +from pico_i2c_lcd import I2cLcd +import constants + + +class Display: + __i2c = I2C( + 0, + sda=Pin(constants.LCD_SDA), + scl=Pin(constants.LCD_SCL), + freq=100000 + ) + __I2C_ADDR = __i2c.scan()[0] + lcd = I2cLcd(__i2c, __I2C_ADDR, 2, 16) + + @staticmethod + def print_pressure(pressure: float): + str_pressure = "{:.3f}".format(pressure) + Display.lcd.putstr('Preassure: ' + str_pressure) + + @staticmethod + def print_frequency(frequency: float): + str_frequency = "{:.3f}".format(frequency) + Display.lcd.puts('Frequency: ' + str_frequency) + + @staticmethod + def print(content: str): + Display.lcd.putstr(content) + + @staticmethod + def clear(): + Display.lcd.clear() diff --git a/src/hall_sensor.py b/src/hall_sensor.py new file mode 100644 index 0000000..8f5c6bf --- /dev/null +++ b/src/hall_sensor.py @@ -0,0 +1,31 @@ +from machine import ADC, Pin +import time +import constants + + +ii = 0 +hall_sensor = Pin(constants.HALL_SENSOR_PIN, Pin.IN, Pin.PULL_UP) +previous_time = time.ticks_ms() + + +def hall_sensor_interruption(*args, **kargs): + global ii + ii += 1 + + +def start_hall_sensor(): + global previous_time + hall_sensor.irq( + handler=hall_sensor_interruption, + trigger=Pin.IRQ_RISING, + ) + previous_time = time.ticks_ms() + time.sleep(0.05) + + +def get_rotation_frequency(): + global ii, previous_time + frequency = float(ii) / (time.ticks_ms() - previous_time) * 1e3 + previous_time = time.ticks_ms() + ii = 0 + return frequency diff --git a/src/main.py b/src/main.py index bda5dba..ed22042 100644 --- a/src/main.py +++ b/src/main.py @@ -1,80 +1,30 @@ from machine import ADC, I2C, Pin, PWM import time -from pico_i2c_lcd import I2cLcd - -pressure_sensor = ADC(Pin(26, mode=Pin.IN)) -hall_sensor = Pin(27, Pin.IN, Pin.PULL_UP) -btnn_reset = Pin(22, Pin.IN) -lcd = False -buzzer_satus = False -ii = 0 - - -def interruption_handler(*args, **kargs): - global ii - ii += 1 - -hall_sensor.irq( - handler=interruption_handler, - trigger=Pin.IRQ_RISING, -) -previous_time = time.ticks_ms() +import hall_sensor +import pressure_sensor +from display import Display +from states import StateMachine def setup(): - global pressure_sensor, lcd, buzzer - i2c = I2C(0, sda=Pin(0), scl=Pin(1), freq=100000) - I2C_ADDR = i2c.scan()[0] - lcd = I2cLcd(i2c, I2C_ADDR, 2, 16) - buzzer = PWM(Pin(15)) - buzzer.duty_u16(0) + Display() + hall_sensor.start_hall_sensor() -def get_rotation_frequency(): - global ii, previous_time - frequency = float(ii) / (time.ticks_ms() - previous_time) * 1e3 - previous_time = time.ticks_ms() - ii = 0 - return frequency - - -def get_pressure(): - global pressure_sensor - pressure_measure = float(pressure_sensor.read_u16()) / 65535 * 3.3 - return pressure_measure - - def loop(): - global lcd, buzzer, buzzer_satus - pressure_measure = get_pressure() - lcd.clear() - str_pressure = "{:.3f}".format(pressure_measure) - lcd.putstr('Preassure: ' + str_pressure + '\n') - frequency = get_rotation_frequency() - str_frequency = "{:.3f}".format(frequency) - lcd.putstr('Frequency: ' + str_frequency) - time.sleep(.07) - buzzer_satus = not buzzer_satus - if buzzer_satus: - buzzer.duty_u16(220) - else: - buzzer.duty_u16(0) - + StateMachine().start_task_loop() def handle_error(): - global lcd - lcd.putstr('Exception') + Display.print('Exception') if __name__ == "__main__": setup() try: - while(True): + while(True): loop() except: handle_error() - while True: - pass - + raise diff --git a/src/parameters.py b/src/parameters.py new file mode 100644 index 0000000..473b51b --- /dev/null +++ b/src/parameters.py @@ -0,0 +1,20 @@ +"""Percentage that the previous pressure can be lower than current one without triggering the hole alarm.""" +PRESSURE_TOLERANCE_PERCENTAGE = 10 + +"""Pressure saturation threshold in mV to trigger the saturation alarm.""" +PRESSURE_SATURATION_THRESHOLD = 2.8 + +"""Lower limit of frequency in Hz to be considered inside interest rotation interval. (type: float)""" +REFERENCE_ROTATION_DOWN_LIMIT = 250.0 + +"""Higher limit of frequency in Hz to be considered inside interest rotation interval. (type: float)""" +REFERENCE_ROTATION_UPPER_LIMIT = 270.0 + +"""Weather or not the buzzer should be set to an annoying tone (in oder to keep the mental healthness of the developer).""" +ANNOYING_BUZZER_ENABLED = False + +"""Period in milisseconds between each storage event.""" +PRESSURE_STORE_PREIOD = int(60*1e3) + +"""How many samples of pressure should be used in smoother.""" +PRESSURE_SMOOTH_LENGTH = 10 diff --git a/src/pressure_sensor.py b/src/pressure_sensor.py new file mode 100644 index 0000000..bb18401 --- /dev/null +++ b/src/pressure_sensor.py @@ -0,0 +1,15 @@ +from machine import ADC, Pin +import constants +import parameters + + +pressure_sensor = ADC(Pin(constants.PRESSURE_SENSOR_PIN, mode=Pin.IN)) + +def get_pressure(): + global pressure_sensor + pressure_value = 0 + for i in range(parameters.PRESSURE_SMOOTH_LENGTH): + pressure_value += pressure_sensor.read_u16() + print(pressure_value / parameters.PRESSURE_SMOOTH_LENGTH) + pressure_measure = float(pressure_value) / constants.ADC_RESOLUTION * 3.3 + return pressure_measure diff --git a/src/states.py b/src/states.py new file mode 100644 index 0000000..f1131e8 --- /dev/null +++ b/src/states.py @@ -0,0 +1,70 @@ +import time +from display import Display +import hall_sensor +import pressure_sensor +import parameters +from alarm import Alarm +from storage import Storage + + +class StateMachine: + pressure= None + + def __init__(self) -> None: + Storage.enable_store_in_next_call() + self.__read_previous_pressure() + self.__measure_pressure() + + def start_task_loop(self): + time.sleep(0.02) + self.wait_for_correct_rotation_frequency() + self.handle_pressure() + + def wait_for_correct_rotation_frequency(self): + while self.__rotation_is_within_limits(): + print('waiting correct rotation') + time.sleep(0.02) + continue + + def __measure_pressure(self): + self.current_pressure = pressure_sensor.get_pressure() + + def handle_pressure(self): + self.__measure_pressure() + is_saturated = self.__pressure_is_higher_than_maximum_saturation() + if is_saturated: + Alarm.trigger_saturated_alarm() + else: + self.scan_holes_in_filter() + + def scan_holes_in_filter(self): + there_is_a_hole = self.__pressure_is_lower_than_before() + if there_is_a_hole: + Alarm.trigger_hole_alarm() + else: + self.__save_current_pressure() + + def __save_current_pressure(self): + Storage.save_pressure(self.current_pressure) + self.previous_pressure = self.current_pressure + + def __read_previous_pressure(self): + self.previous_pressure = Storage.retrieve_pressure() + + def __pressure_is_lower_than_before(self) -> bool: + pressure_is_lower = ( + self.current_pressure < + self.previous_pressure * (1 - parameters.PRESSURE_TOLERANCE_PERCENTAGE / 100) + ) + return pressure_is_lower + + def __pressure_is_higher_than_maximum_saturation(self) -> bool: + pressure_is_not_lower = ( + self.current_pressure > parameters.PRESSURE_SATURATION_THRESHOLD + ) + return pressure_is_not_lower + + def __rotation_is_within_limits(self) -> bool: + rotation = hall_sensor.get_rotation_frequency() + is_within_limits = parameters.REFERENCE_ROTATION_DOWN_LIMIT < rotation < parameters.REFERENCE_ROTATION_UPPER_LIMIT + return is_within_limits diff --git a/src/storage.py b/src/storage.py new file mode 100644 index 0000000..a0de4f0 --- /dev/null +++ b/src/storage.py @@ -0,0 +1,43 @@ +import json +from machine import Timer +import parameters + + +class __CONSTANTS: + PREVIOUS_PRESSURE = 'previous_pressure' + STORAGE_FILE_PATH = 'storage.json' + + +class Storage: + + __should_store = False + + @staticmethod + def enable_store_in_next_call(): + Storage.__should_store = True + + @staticmethod + def start_storage(): + def timer_call_back(): + Storage.enable_store_in_next_call() + tim = Timer() + tim.init(mode=Timer.PERIODIC, period=parameters.PRESSURE_STORE_PREIOD, callback=timer_call_back) + + + @staticmethod + def save_pressure(pressure: float): + if not Storage.__should_store: + return + content = { + __CONSTANTS.PREVIOUS_PRESSURE: pressure, + } + with open(__CONSTANTS.STORAGE_FILE_PATH, 'w') as f: + json.dump(content, f) + + @staticmethod + def retrieve_pressure() -> float: + pressure = None + with open(__CONSTANTS.STORAGE_FILE_PATH, 'r') as f: + content = json.load(f) + pressure = content[__CONSTANTS.PREVIOUS_PRESSURE] + return pressure