If you have time/are willing to take a look at my code for memory optimization opportunities, feedback will be appreciated. Please be kind, I generally code in C, and I’m not that great at it anyway.
Note that if I take this code and remove the comments, it will fit. But barely. If I add any more functionality, I hit the memory error again.
And yes, I am barely using the functionality of the OpenMV cam, but it is perfect for what I need it to do (color analysis). Unfortunately I can’t use your canned LAB-based image analytics, this is why I am averaging pixels across a ROI.
import sensor, image, time, pyb
from pyb import I2C, delay, ExtInt, Pin, Timer
global systemState #increments state
systemState = 0x00
#flag toggled in ISR when start button is pressed
startButtonPressFlag = 0
#keeps track of the start button state - used to keep tabs on a long press to handle a user-forced reset condition
isStartBtnPressed = 0
#keeps track of the time that the start button is depressed
startBtnPressDuration = 0
#used in each state to keep track of timed events
sysClockDuration = 0
#indicates if the arm motor is on or off
isArmMotorOn = 0
#indicates if the power up state is complete - the system then waits for user input
isPwrUpComplete = 0
#indicates if the cleaning pump is on or off
isClnPumpComplete = 0
#indicates if the urine pump is on or off
isUrnPumpComplete = 0
#indicates if the test chamber is filled or not
isChamberFilled = 0
#indicates if the urine hold duration (in the test chamber) is complete
isHoldComplete = 0
#indicates if the drain in the urine accumulator is open or not
isDrainOpen = 0
#indicates if the urine pre-drain interval is complete (mid-stream urine is used for analysis_
hasPreDrainOccured = 0
#indicates if the colormetric test is complete or not
isColormetricComplete = 0
#indicates if the collector arm is home or not
isArmHome = 0
#used in each state to initialize the state as needed
init = 0
#used during i2c transactions
snd_data = bytearray(1)
recv_data = bytearray(1)
#variable updated during colormetric tests to reflect the range of interest being analyzed in the image
roi = (0,0,0)
#clock used for timing button events
buttonClock = time.clock()
#interrupt handler - only used to handle button presses (currently)
def intHandler():
global startButtonPressFlag
global isStartBtnPressed
global startBtnPressBuration
global systemState
if startButtonPressFlag == 1:
#if the start pin is high (switch is NC) and the flag is low, the button is pressed
if startButtonPin.value() == 1 and isStartBtnPressed == 0:
isStartBtnPressed = 1
buttonClock.tick()
extIntStart.enable()
#else if the pin is high and the flag is asserted, look at the button timer
elif startButtonPin.value() == 1 and isStartBtnPressed == 1:
startBtnPressDuration = buttonClock.avg()
#if the button is held for longer than interval, a forced reset is issued
if startBtnPressDuration > 2000:
systemState = 0x08
buttonClock.reset()
startButtonPressFlag = 0
#if the pin is low and the flag is asserted, the button is released
elif startButtonPin.value() == 0 and isStartBtnPressed == 1:
isStartBtnPressed = 0
buttonClock.reset()
startButtonPressFlag = 0
extIntStart.enable()
#camera initialization routine
def cameraInit():
sensor.reset() # Reset and initialize the sensor.
sensor.set_pixformat(sensor.RGB565) # Set pixel format to RGB565
sensor.set_framesize(sensor.VGA) # Set frame size to VGA
sensor.set_brightness(0) #+/-3
sensor.set_saturation(0) #+/-3
sensor.set_contrast(0) #+/-3
sensor.set_auto_gain(False, -10) #gain_db
sensor.set_auto_exposure(False, 15000) #exposure time
sensor.set_quality(100)
#start button ISR
def startbuttonISR(line):
global startButtonPressFlag
startButtonPressFlag = 1
extIntStart.disable()
def runArmOut():
snd_data[0] = 0xFD
i2c.mem_write(snd_data, 0x64, 0, timeout=2000, addr_size=8) #arm motor
#function which drives the urine collection arm back towards the home position
def runArmBack():
snd_data[0] = 0xFE
i2c.mem_write(snd_data, 0x64, 0, timeout=2000, addr_size=8) #arm motor
#stops the urine collection arm
def stopArm():
snd_data[0] = 0x00
i2c.mem_write(snd_data, 0x64, 0, timeout=2000, addr_size=8) #arm motor
#pumps urine in to the test chamber
def pumpFluidIn():
snd_data[0] = 0xFE
i2c.mem_write(snd_data, 0x63, 0, timeout=2000, addr_size=8) #pump motor
def pumpFluidOut():
snd_data[0] = 0xFD
i2c.mem_write(snd_data, 0x63, 0, timeout=2000, addr_size=8) #pump motor
#stops the urine pump
def stopPump():
snd_data[0] = 0x00
i2c.mem_write(snd_data, 0x63, 0, timeout=2000, addr_size=8) #pump motor
#opens the drain in the urine collection chamber (accumulator)
def openDrain():
snd_data[0] = 0xF7
i2c.mem_write(snd_data, 0x20, 0, timeout=2000, addr_size=8) #output expander
def closeDrain():
snd_data[0] = 0xFF
i2c.mem_write(snd_data, 0x20, 0, timeout=2000, addr_size=8) #output expander
#enables the cleaning pump
def cleanActive():
cleanPumpPin.high()
def cleanInactive():
cleanPumpPin.low()
#used to write the results of the colormetric analysis
def uartWrite(rAvg, gAvg, bAvg, result):
uart.write("%d"%rAvg)
uart.write(",")
uart.write("%d"%gAvg)
uart.write(",")
uart.write("%d"%bAvg)
uart.write(",")
uart.write("%d"%result)
uart.write("\n\r")
def analyzeVitC(gAvg):
if gAvg>=168:
return 0
elif gAvg>=160:
return 50
elif gAvg>=150:
return 100
elif gAvg>=134:
return 200
elif gAvg>=119:
return 300
elif gAvg>=94:
return 500
elif gAvg>=76:
return 700
elif gAvg>=58:
return 1000
else:
return 2000
def analyzeB7(gAvg):
if gAvg>=185:
return 20
if gAvg>=177:
return 15
if gAvg>=170:
return 10
elif gAvg>=162:
return 5
else:
return 0
def analyzeMag(gAvg):
if gAvg>=185:
return 0
if gAvg>=177:
return 50
if gAvg>=170:
return 250
elif gAvg>=162:
return 450
else:
return 1500
def analyzePH(gAvg):
if gAvg>=168:
return 0
elif gAvg>=160:
return 5
elif gAvg>=150:
return 5.5
elif gAvg>=134:
return 6
elif gAvg>=119:
return 6.5
elif gAvg>=94:
return 7
elif gAvg>=76:
return 7.5
elif gAvg>=58:
return 8
else:
return 8.5
def analyzeSG(gAvg):
if gAvg>=168:
return 1000
elif gAvg>=160:
return 1005
elif gAvg>=150:
return 1010
elif gAvg>=134:
return 1015
elif gAvg>=119:
return 1020
elif gAvg>=94:
return 1025
else:
return 1030
def analyzeColor(gAvg):
if gAvg>=168:
return 1
elif gAvg>=160:
return 2
elif gAvg>=150:
return 3
elif gAvg>=134:
return 4
elif gAvg>=119:
return 5
elif gAvg>=94:
return 6
elif gAvg>=84:
return 7
else:
return 8
def analyzeArea(roi):
cameraInit() #initialize the camera
sensor.set_windowing(roi) #specify the region of interest
sensor.skip_frames(time = 2000) # Wait for settings take effect.
img = sensor.snapshot() # Take a picture and return the image.
delay(100)
rAvg = 0
gAvg = 0
bAvg = 0
#sum the pixels in the range of interest
for x in range(roi[2]):
for y in range(roi[3]):
pixel = img.get_pixel(x, y, rgbtuple=True)
#delay(10)
rAvg = rAvg+pixel[0];
gAvg = gAvg+pixel[1];
bAvg = bAvg+pixel[2];
#average the pixels
rAvg = rAvg/(roi[2]*roi[3])
gAvg = gAvg/(roi[2]*roi[3])
bAvg = bAvg/(roi[2]*roi[3])
del img #img is no longer needed
gc.collect #collect the garbage
rgbAverage = (rAvg, gAvg, bAvg)
return rgbAverage
#Initialization
uart = pyb.UART(1, 115200, timeout_char = 1000) #pin 0 = RX, pin 1 = TX - used for comms of the results
#pin 2 - not used
portExpInt = pyb.Pin('P3', pyb.Pin.IN) #not currently used
startButtonPin = pyb.Pin('P4', pyb.Pin.IN) #start button pin - currently NC
homeButtonPin = pyb.Pin('P5', pyb.Pin.IN) #note, home switch is NC
magProxPin = pyb.Pin('P6', pyb.Pin.IN) #magnet proximity switch - hall effect sensor
i2c = I2C(4) # create on bus 4 - pin 7 = SCL, pin 8 = SDA
i2c = I2C(4, I2C.MASTER) # create and init as a master
i2c.init(I2C.MASTER, baudrate=100000)
cleanPumpPin = pyb.Pin('P9', pyb.Pin.OUT_PP)
display_led = pyb.LED(1)
green_led = pyb.LED(2)
blue_led = pyb.LED(3)
red_led = pyb.LED(4)
extIntStart = pyb.ExtInt(startButtonPin, pyb.ExtInt.IRQ_RISING_FALLING, pyb.Pin.PULL_NONE, startbuttonISR)
#make sure everything is off upon startup
stopArm()
stopPump()
cleanPumpPin.low()
closeDrain()
green_led.off()
blue_led.off()
red_led.on() #swapped polarity - must confirm
display_led.off() #swapped polariy
sysClock = time.clock() #initialize system clock
while(True):
#if the button press flag is issued, run the interrupt handler
if startButtonPressFlag == 1:
intHandler()
if systemState == 0x00: #Power up state
if init == 0: #Initialization
init = 1
sysClock.tick()
red_led.off() #red LED on, boot up in progress, note swapped polarity
else:
sysClockDuration = sysClock.avg()
if isPwrUpComplete == 0:
#if power up is not complete, look at the home pin - if 6000ms elapses and the arm still isn't home, go to an error state
if homeButtonPin.value() == 0 and sysClockDuration<6000: #ARM_RETURN_MAX_DURATION
#if the home pin indicates that the arm isn't home, send it home
if isArmMotorOn == 0:
isArmMotorOn = 1
runArmBack()
elif homeButtonPin.value() == 0 and sysClockDuration>=6000: #ARM_RETURN_MAX_DURATION
systemState = 0x08 #error state
init = 0
sysClock.reset()
stopArm()
elif homeButtonPin.value() == 1:
isArmMotorOn = 0
stopArm()
isPwrUpComplete = 1
else:
red_led.on() #swapped polarity
green_led.on() #green LED - ready for user input
#waiting for start button press
if isStartBtnPressed == 1:
systemState = 0x01 #sweep state
init = 0
isPwrUpComplete = 0
sysClock.reset()
#Timing for sweeping the arm out
elif systemState == 0x01:
if init == 0:
init = 1
sysClock.tick()
#send arm out
runArmOut()
red_led.on() #red and green on - amber - filling
else:
sysClockDuration = sysClock.avg()
#arm is in position
if sysClockDuration >= 1500 and homeButtonPin.value() == 0: #ARM_SWEEP_OUT_DURATION
stopArm()
init = 0
sysClock.reset()
systemState = 0x02 #urine collect state
#arm never left - error
elif sysClockDuration >= 1200 and homeButtonPin.value() == 1: #ARM_SWEEP_OUT_DURATION
stopArm()
init = 0
sysClock.reset()
systemState = 0x08 #error state
#Timing for urine collection
elif systemState == 0x02:
if init == 0:
init = 1
sysClock.tick()
else:
sysClockDuration = sysClock.avg()
#if 7000ms elapses without a mag sensor toggle - error
if sysClockDuration >=7000 and hasPreDrainOccured == 0: #MAX_COLLECT_DURATION
init = 0
sysClock.reset()
systemState = 0x08 #error state
#if the mag sensor toggles and pre-draining hasn't completed yet, open the drain
elif magProxPin.value() == 0 and hasPreDrainOccured == 0:
sysClock.reset()
sysClock.tick()
openDrain()
hasPreDrainOccured = 1
#close the drain after time has elapsed
elif hasPreDrainOccured == 1 and sysClockDuration >=300 and sysClockDuration < 350:
closeDrain()
#when the mag sensor is toggled again (or if it is maintained) - move state
elif magProxPin.value() == 0 and hasPreDrainOccured == 1 and sysClockDuration >=350:
init = 0
hasPreDrainOccured = 0
sysClock.reset()
systemState = 0x03
red_led.off()
green_led.off()
blue_led.on() #blue LED on - urine collected, pumping to chamber
#timing to sweep the arm back while pumping urine in to the test chamber
elif systemState == 0x03:
if init == 0:
init = 1
sysClock.tick()
runArmBack()
pumpFluidIn()
else:
sysClockDuration = sysClock.avg()
#if the arm doesn't return in 6000ms - issue an error
if sysClockDuration >= 6000: #ARM_RETURN_MAX_DURATION
init = 0
sysClock.reset()
systemState = 0x08 #error state
stopArm()
stopPump()
#stop the arm once the home switch is toggled
elif homeButtonPin.value() == 1 and isArmHome == 0:
stopArm()
isArmHome = 1
#stop the pump after the pre-determined time
if sysClockDuration >= 2500 and isUrnPumpComplete == 0: #URINE_PUMPING_DURATION
isUrnPumpComplete = 1
stopPump()
#After the urine has been held in the test chamber for the allotted time, move to the next state
if isArmHome == 1 and isUrnPumpComplete == 1 and sysClockDuration >= (4500): #URINE_PUMPING_DURATION + URINE_HOLD_DURATION
init = 0
isArmHome = 0
isUrnPumpComplete = 0
sysClock.reset()
red_led.on()
green_led.on() #amber - processing urine
blue_led.off()
#Perform color test while urine is in the chamber
roi = (511, 443, 32, 27) #ROI COLOR - must be updated
RGBaverage = analyzeArea (roi)
#apply scaling factor to RGB data - placeholder
result = analyzeColor(RGBaverage[1])
uart.write("TEST_BEGIN\n\r")
uart.write("COLOR,")
uart.write(RGBaverage[0], RGBaverage[1], RGBaverage[2], result)
systemState = 0x04
elif systemState == 0x04: #drain urine
if init == 0:
init = 1
sysClock.tick()
pumpFluidOut()
openDrain()
else:
sysClockDuration = sysClock.avg()
#turn off pump after elapsed interval
if sysClockDuration >= 4500 and isUrnPumpComplete == 0: #URINE_DRAINING_DURATION
isUrnPumpComplete = 1
stopPump()
#close the drain after the elapsed interval
elif sysClockDuration >= (6500) and isUrnPumpComplete == 1: #URINE_DRAINING_DURATION + ACCUMULATOR_DRAINING_DURATION
closeDrain()
init = 0
isUrnPumpComplete = 0
sysClock.reset()
systemState = 0x05
elif systemState == 0x05: #pre-clean state - flush the collector and the accumulator
if init == 0:
init = 1
sysClock.tick()
cleanActive()
else:
sysClockDuration = sysClock.avg()
#run the cleaning pump for the alotted time
if sysClockDuration >= 3000 and isClnPumpComplete == 0: #CLEANING_FILL_DURATION
isClnPumpComplete = 1
cleanInactive()
#Let the cleaning solution sit in the accumulator for a bit, then drain
elif sysClockDuration >= (7000) and isClnPumpComplete == 1 and isDrainOpen == 0: #CLEANING_FILL_DURATION + CLEANING_HOLD_DURATION
isDrainOpen = 1
openDrain()
#close the drain after the alotted time
elif sysClockDuration >= (9000) and isDrainOpen == 1: #CLEANING_FILL_DURATION + CLEANING_HOLD_DURATION + ACCUMULATOR_DRAINING_DURATION
closeDrain()
init = 0
isClnPumpComplete = 0
isDrainOpen = 0
sysClock.reset()
systemState = 0x06
elif systemState == 0x06: #colormetric analysis
if init == 0:
init = 1
sysClock.tick()
else:
sysClockDuration = sysClock.avg()
if isColormetricComplete == 0:
for i in range (0,4): #step through the 5x regions of interest that can be analyzed right away
if i==0:
roi = (53, 391, 35, 33) #ROI ASCORBIC ACID
elif i==1:
roi = (150, 408, 37, 32) #ROI MAGNESIUM
elif i==2:
roi = (345, 417, 32, 36) #ROI PH
elif i==3:
roi = (444, 414, 27, 33) #ROI SG
RGBaverage = analyzeArea (roi)
result = 0
if i==0:
result = analyzeVitC(RGBaverage[1])
uart.write("Vitamin C,")
elif i==1:
result = analyzeMag(RGBaverage[1])
uart.write("Magnesium,")
elif i==2:
result = analyzePH(RGBaverage[1])
uart.write("PH,")
elif i==3:
result = analyzeSG(RGBaverage[1])
uart.write("SG,")
uartWrite(RGBaverage[0], RGBaverage[1], RGBaverage[2], result)
isColormetricComplete = 1
sysClock.reset()
sysClock.tick()
else:
if sysClockDuration>=450000: #B7_WAIT_DURATION - this is about 13 minutes - timekeeping is not great
roi = (258, 80, 26, 41) #ROI B7 - may need to be tweaked
RGBaverage = analyzeArea (roi)
result = analyzeB7(RGBaverage[1])
uart.write("B7,")
uartWrite(RGBaverage[0], RGBaverage[1], RGBaverage[2], result)
uartWrite("TEST_END\n\r")
init = 0
isColormetricComplete = 0
sysClock.reset()
systemState = 0x07 #move to final cleaning step
red_led.off()
green_led.on() #green - processing complete
blue_led.off()
elif systemState == 0x07: #final clean
if init == 0:
init = 1
sysClock.tick()
cleanActive()
else:
sysClockDuration = sysClock.avg()
#run the cleaning pump for the specified duration
if sysClockDuration >= 3000 and isClnPumpComplete == 0: #CLEANING_FILL_DURATION
isClnPumpComplete = 1
cleanInactive()
pumpFluidIn()
#use the urine pump to pump cleaning fluid in to the test chamber
elif sysClockDuration >= (5750) and isClnPumpComplete == 1 and isChamberFilled == 0: #CLEANING_FILL_DURATION + CLEANING_PUMPING_DURATION
isChamberFilled = 1
stopPump()
#hold the cleaning solution in the test chamber
elif sysClockDuration >= (9750) and isChamberFilled == 1 and isHoldComplete == 0: #CLEANING_FILL_DURATION + CLEANING_HOLD_DURATION + CLEANING_PUMPING_DURATION
isHoldComplete = 1
pumpFluidOut()
openDrain()
#drain the fluid from the test chamber
elif sysClockDuration >= (15750) and isHoldComplete == 1: #CLEANING_FILL_DURATION + CLEANING_HOLD_DURATION + CLEANING_PUMPING_DURATION + CLEANING_DRAINING_DURATION
closeDrain()
stopPump()
init = 0
isClnPumpComplete = 0
isChamberFilled = 0
isHoldComplete = 0
sysClock.reset()
systemState = 0x00 #return to power up
elif systemState == 0x08: #error state
red_led.off() #note polarity shift
green_led.off()
blue_led.off()
#sweep arm back until the home switch toggles
runArmBack()
while homeButtonPin.value() == 0:
delay(20)
stopArm()
pumpFluidOut()
openDrain()
delay(4000)
closeDrain()
stopPump()
cleanActive()
delay(3000)
cleanInactive()
pumpFluidIn()
delay(5550)
stopPump()
delay(4000)
pumpFluidOut()
openDrain()
delay(10000)
stopPump()
closeDrain()
systemState = 0x00
delay(20)