Serial output and ISR

The official pyboard running MicroPython.
This is the reference design and main target board for MicroPython.
You can buy one at the store.
Target audience: Users with a pyboard.
Post Reply
dwculp
Posts: 12
Joined: Wed Jul 13, 2016 6:43 pm

Serial output and ISR

Post by dwculp » Mon Jul 25, 2016 3:47 am

I am writing an interface/driver for an Adafruit serial LCD. It is a simple device that requires only one serial wire to operate. It accepts simple byte sized commands to control the LCD. I have it working, it works great. However, in my test setup I also have an ISR that reads a switch. The only thing it does is increment a counter to keep track of the number of times the switch has been pressed and released.

Code: Select all

#=======================================================================
# Callback for our leaf switch
#=======================================================================
def leafCallback(ISRline):
    global leafCounter
  
    #leafInt.disable() # disable this interrupt while we are in it
    pyb.delay(3) #delay for debounce  
    if leafPin.value() == 1:
        leafCounter += 1
        #print ("Leaf count: ",leafCounter)
    #leafInt.enable() # renable the ISR
I have a 10K pot hooked to one of the onboard ADCs. In a while loop one of the things I do is read the pot, scale the output into a number from 0-255 and set the LCD backlight red color to that value. This is a serial operation and it works great until I hit my leaf switch. When the ISR triggers I will sometimes (actually MOST of the time) get the following error:

Code: Select all

OSError: [Errno 110] ETIMEDOUT
If I press the switch slowly I can usually get quite a few presses, rapid presses will result in the error. I tried a simple try-except in my lcd class as follows:

Code: Select all

    def setBacklightColor(self,r,g,b):
		try:
			self.sendCommand(SET_BACKLIGHT_COLOR)
			self.uart.writechar(r)
			self.uart.writechar(g)
			self.uart.writechar(b)
		except:
			print ("timeout error!")
It catches the error but the serial comms are a bit messed up.

Any ideas what to do?

User avatar
dhylands
Posts: 3821
Joined: Mon Jan 06, 2014 6:08 pm
Location: Peachland, BC, Canada
Contact:

Re: Serial output and ISR

Post by dhylands » Mon Jul 25, 2016 5:40 am

Are you doing the serial write from within an IRQ?

What is your timeout and timeout_char values for the UART (when its initialized)? What baud rate are you using?

I noticed you calling pyb.delay (with disabling interrupts commented out). You can't use pyb.delay with interrupts disabled (because pyb.delay needs the millisecond timer interrupt to advance the tick counter used).

dwculp
Posts: 12
Joined: Wed Jul 13, 2016 6:43 pm

Re: Serial output and ISR

Post by dwculp » Tue Jul 26, 2016 12:19 am

Im not doing the serial writes inside an ISR, they take place inside of a while loop in main.py. I have experimented with timeout values all the way up to 1000ms and I always get the error. Here is the complete serial LCD class in its current rough state:

Code: Select all

'''This is a simple class for interfacing an Adafruit serial LCD with a pyboard.'''

import pyb

#define our LCD commands
LCD_COMMAND             =   0xFE
DISPLAY_ON              =   0x42 
DISPLAY_OFF             =   0x46
SET_BRIGHTNESS          =   0x99
SET_SAVE_BRIGHTNESS     =   0x98
SET_CONTRAST            =   0x50
SET_SAVE_CONTRAST       =   0x91
AUTOSCROLL_ON           =   0x51
AUTOSCROLL_OFF          =   0x52
CLEAR_DISPLAY           =   0x58
SET_SPLASH_SCREEN       =   0x40
SET_CURSOR_POS          =   0x47
GO_HOME                 =   0x48
CURSOR_BACK             =   0x4C
CURSOR_FORWARD          =   0x4D
UNDERLINE_CURSOR_ON     =   0x4A
UNDERLINE_CURSOR_OFF    =   0x54
BLOCK_CURSOR_ON         =   0x53
BLOCK_CURSOR_OFF        =   0x54
SET_BACKLIGHT_COLOR     =   0xD0
SET_LCD_SIZE            =   0xD1
CREATE_CUSTOM_CHAR      =   0x4E
SAVE_CUSTOM_CHAR        =   0XC1
LOAD_CUSTOM_CHAR        =   0XC0
SET_GPO_OFF             =   0x56
SET_GPO_ON              =   0X57

class AfSerialLCD:
    def __init__(self, uartNum, timeout=1000, timeout_char=1000, baud=9600 ):
        if uartNum == 5 or uartNum < 1 or uartNum > 6:
            raise ValueError
        self.uart = pyb.UART( uartNum, baud) 
    
    def sendCommand(self, command):
        try:
            self.uart.writechar(LCD_COMMAND)
            self.uart.writechar(command)
        except:
            print ("timeout error!")
    
    def writeString(self,string):
        self.uart.write(string)
        
    def clearScreen(self):
        self.sendCommand(CLEAR_DISPLAY)
        
    def setContrast(self,contrast, save=False):
        if save == False:
            self.sendCommand(SET_CONTRAST)
        else:
            self.sendCommand(SET_SAVE_CONTRAST)
        self.uart.writechar(contrast)
    
    def setBrightness(self,brightness, save=False):
        if save == False:
            self.sendCommand(SET_BRIGHTNESS)
        else:
            self.sendCommand(SET_SAVE_BRIGHTNESS)
        self.uart.writechar(brightness)
        
    def setSaveBrigthness(self, brightness):
        self.sendCommand(SET_SAVE_BRIGHTNESS)
        self.uart.writechar(brightness)
        
    def displayOn(self):
        self.sendCommand(DISPLAY_ON)
    
    def displayOff(self):
        self.sendCommand(DISPLAY_OFF)
        
    def home(self):
        self.sendCommand(GO_HOME)
    
    def setCursorPosition(self, row,col):
        self.sendCommand(SET_CURSOR_POS)
        self.uart.writechar(col)
        self.uart.writechar(row)
    
    def setBacklightColor(self,r,g,b):
        
        try:
            self.sendCommand(SET_BACKLIGHT_COLOR)
            self.uart.writechar(r)
            self.uart.writechar(g)
            self.uart.writechar(b)
        except:
            print ("timeout error!")    
    
    def setAutoscroll(self, scrollState):
        if scrollState == True:
            self.sendCommand(AUTOSCROLL_ON)
        else:
            self.sendCommand(AUTOSCROLL_OFF)
            
    def setSplashScreen(self, splashMsg):
        self.sendCommand(SET_SPLASH_SCREEN)
        self.uart.write(splashMsg)
    
    def cursorBack(self):
        self.sendCommand(CURSOR_BACK )
        
    def cursorForward(self):
        self.sendCommand(CURSOR_FORWARD)
        
    def setUnderlineCursor(self, state=True):
        if state == True:
            self.sendCommand(UNDERLINE_CURSOR_ON)
        else:
            self.sendCommand(UNDERLINE_CURSOR_OFF)
    
    def setBlockCursor(self, state=True):
        if state == True:
            self.sendCommand(BLOCK_CURSOR_ON)
        else:
            self.sendCommand(BLOCK_CURSOR_OFF)
    
    def setLCDSize(self, rows=2, cols=16):
        self.sendCommand(SET_LCD_SIZE)
        self.uart.writechar(rows)
        self.uart.writechar(cols)
        
    def setGPOState(self, gpoNum, state=False):
        if gpoNum < 1 or gpoNum > 4:
            print("ERROR: gpoNum must be 1-4 in setGPOState")
            return
        if state == False:
            self.sendCommand(SET_GPO_OFF)
            self.uart.writechar(gpoNum)
            return
        elif state == True:
            self.sendCommand(SET_GPO_ON)
            self.uart.writechar(gpoNum)
            return 
        else:
            print("ERROR: Unknown state for setGPOState.")
            
    def createCustomChar(self, charNum, byteList):
        if charNum < 0 or chatNum > 7:
            print("ERROR: charnum must be 0-7 in createCustomChar.")
            return
        if len(byteList) > 8:
            print("ERROR: byteList must be list of 8 buyes in createCustomChar.")
            return
        self.sendCommand(CREATE_CUSTOM_CHAR)
        for char in byteList:
            self.uart.writechar(char)
Inside of main.py I create an AfSerialLCD object:

Code: Select all

lcd = AfSerialLCD(6,9600)
I have a bunch of stuff to test out various capabilities of the Pyboard:
1. The serial LCD of course.
2. A LDR hooked up to one of the ADC lines
3. An LED hooked up to a PWM output. I read the LDR and feed its scaled output inte the LED.
4. An Omrom EE-SB5 hooked into another ADC pin. Its a simple IR reflectance sensor.
5. The leaf switch which is hooked into my ISR.
6. A 10K pot. The reading of the pot gets scaled and sent to the serial LCD as the red value of the backlight.
7. Finally another LED which is on when the leaf switch is pressed, off otherwise.

The main while loop looks like the following:

Code: Select all

#start our ISR
leafInt = pyb.ExtInt(leafPin, pyb.ExtInt.IRQ_RISING_FALLING, pyb.Pin.PULL_DOWN, leafCallback)
try:
    while(True):
        photoVal = photoResistor.read()
        topHatVal = topHat.read()
        potVal = pot.read()
        lcd.setBacklightColor( int(remap(potVal, 0,4095,0,255)),0,0)
        pwmPercent = int( remap(photoVal, photoLow, photoHigh, 0, 100) )
        leds [randRange(0,4)].toggle()
        pyb.delay( randRange(low, high) )
        y3LED.value(leafPin.value())
        if sw() == True:
            print ( "Photo:",photoVal,"\tTop Hat:",topHatVal,"\tPot:",potVal,"\tPWM:",pwmPercent )
        ch.pulse_width_percent( pwmPercent )
        
finally:
    for led in leds:
        led.off()
    y3LED.value(False)
    leafInt.disable()
    ch.pulse_width_percent(0)

The "lcd.setBacklightColor( int(remap(potVal, 0,4095,0,255)),0,0)" is where the serial write will occur.

And finally, the (cleaned up) ISR for the leaf switch looks like this:

Code: Select all

#=======================================================================
# Callback for our leaf switch
#=======================================================================
def leafCallback(line):
    global leafCounter
    
    pyb.delay(3) #delay for debounce  
    if leafPin.value() == 1:
        leafCounter += 1
        #print ("Leaf count: ",leafCounter)
I can write to the serial LCD all day long and it works as long as the ISR is not both active and triggered. However, once I put the serial write inside of the while loop if the ISR triggers it has a chance of throwing the timeout error.

User avatar
dhylands
Posts: 3821
Joined: Mon Jan 06, 2014 6:08 pm
Location: Peachland, BC, Canada
Contact:

Re: Serial output and ISR

Post by dhylands » Tue Jul 26, 2016 4:33 am

I tried simplifying your code, and by the time I got something that would work, I couldn't reproduce the problem.

Which version of MicroPython are you using?

The only thing that raises a red flag for me is calling delay inside an ISR. It works, but causes IRQ of that level or lower to be disabled while waiting for the delay to finish. But nothing else jumped out at me to explain why it would cause a timeout on the uart.

dwculp
Posts: 12
Joined: Wed Jul 13, 2016 6:43 pm

Re: Serial output and ISR

Post by dwculp » Tue Jul 26, 2016 5:01 am

I am running the following:

MicroPython v1.8.2-11-gbe313ea on 2016-07-13; PYBv1.1 with STM32F405RG

Getting rid of the call to delay fixes the problem. I will implement a routine to debounce the switch without using a delay.

dwculp
Posts: 12
Joined: Wed Jul 13, 2016 6:43 pm

Re: Serial output and ISR

Post by dwculp » Tue Jul 26, 2016 8:06 pm

I changed the ISR to the below code and it seems to work perfectly:

Code: Select all

#=======================================================================
# Callback for our leaf switch
#=======================================================================

#Some global variables for our callback
leafCounter = 0 
oldLeafState = 0
leafPressTime = 0

def leafCallback(line):
    global leafCounter, oldLeafState, leafPressTime
    
    leafState = leafPin.value()
 
    if leafState != oldLeafState:
        if pyb.elapsed_micros(leafPressTime) >= 1500:
            leafPressTime = pyb.micros()
            oldLeafState = leafState
            if leafState == 1:
                leafCounter += 1

Post Reply