Browse Source

Modularized state machine working stable.

main
gabriel becker 2 years ago
parent
commit
2c951c7324
  1. 18
      README.md
  2. 37
      src/alarm.py
  3. 1
      src/boot.py
  4. 22
      src/buzzer.py
  5. 15
      src/constants.py
  6. 32
      src/display.py
  7. 31
      src/hall_sensor.py
  8. 70
      src/main.py
  9. 20
      src/parameters.py
  10. 15
      src/pressure_sensor.py
  11. 70
      src/states.py
  12. 43
      src/storage.py

18
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)
- 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)

37
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

1
src/boot.py

@ -0,0 +1 @@
# boot.py -- run on boot-up

22
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)

15
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

32
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()

31
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

70
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

20
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

15
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

70
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

43
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
Loading…
Cancel
Save