Netduino home hardware projects downloads community

Jump to content


The Netduino forums have been replaced by new forums at community.wildernesslabs.co. This site has been preserved for archival purposes only and the ability to make new accounts or posts has been turned off.
Photo

wait on interrupt

netduino interrupt thread

  • Please log in to reply
13 replies to this topic

#1 stotech

stotech

    Advanced Member

  • Members
  • PipPipPip
  • 143 posts
  • LocationAustralia

Posted 30 September 2014 - 12:19 AM

OK, I have a good look and can't find and answer to this so I hope it's not real obvious.

 

I'm working on this really cool break out board (attached) for the N+2, it just uses the spi bus to break out a heap of other cool features.

 

RS232

couple of LCD's

8 heavy opto isolated FET output's.

bunch of switches and more isolated IO then you can poke a stick at.

 

Attached File  DSCF2455-2.jpg   108.25KB   3 downloads

 

It's meant to be robust and is for those, "one off" quick solutions that you can make a quick buck off. No where near ready though. 

 

The 20x4 character LCD has a wicked cool 4 line menu system that I'm really proud of.

It consist's of 6 buttons, up, line 1, 2, 3, 4 and down.

It navigates through a 3 dimensional array that is like the folder structure. Down cycles through the current directory and up goes back to the parent directory. Selecting one of the line buttons either enters that directory (if it is a folder), or runs the associated thread to it.

 

Now that you know the background, my question is this. On previous revisions I just scanned the button's for input's, but now I've got them tied to actual interrupts because i thought it would be better(now i'm wondering though). This creates a new challenge for me because the roles of the switches are constantly evolving and I've only ever dealt with interrupt's in a static manner. Here is an example of the code.

private static void on_keyTimerTick(object state)
        {
            doMenuStuff();
        }

        private static Keys GetKey()
        {

            bool up = Multi.Switch[5].Read();
            bool line0 = Multi.Switch[4].Read();
            bool line1 = Multi.Switch[3].Read();
            bool line2 = Multi.Switch[2].Read();
            bool line3 = Multi.Switch[1].Read();
            bool down = Multi.Switch[0].Read();


            if (up == !true)
            {
                return Keys.Up;
            }
            else if (line0 == !true)
            {
                return Keys.Line0;
            }
            else if (line1 == !true)
            {
                return Keys.Line1;
            }
            else if (line2 == !true)
            {
                return Keys.Line2;
            }
            else if (line3 == !true)
            {
                return Keys.Line3;
            }
            else if (down == !true)
            {
                return Keys.Down;
            }
            else
            {
                return Keys.None;
            }
        }

        private static void doMenuStuff()
        {
            switch (MENU.GetKey())
            {
                case MENU.Keys.None:
                    break;

                case MENU.Keys.Up:
                    //Debug.Print("up");
                    if (line[0].menuTag != myMenu[0].menuTag || line[0].menuTag == "Blank")
                    {
                        cancelMenu();
                    }
                    break;

                case MENU.Keys.Down:
                    //Debug.Print("down");
                    if (line[3].menuTag != "Blank")
                    {
                        currentMenu = line[3];
                        getNextMenuItem();
                    }
                    
                    if (line[3].menuTag == "Blank" & line[0].order == 0 || currentMenu.menuTag == line[0].menuTag)
                    {
                        break;
                    }
                    else if (line[3].menuTag == "Blank" & line[0].order != 0)
                    {
                        DisplayMenu(currentMenu, true);
                        break;
                    }
                    else
                    {
                        DisplayMenu(currentMenu);
                    }
                    break;

                case MENU.Keys.Line0:
                    //Debug.Print("Line0");
                    if (line[0].menuTag != "Blank")
                    {
                        currentMenu = line[0];
                        selectMenu();
                    }
                    break;

                case MENU.Keys.Line1:
                    //Debug.Print("Line1");
                    if (line[1].menuTag != "Blank")
                    {
                        currentMenu = line[1];
                        selectMenu();
                    }
                    break;

                case MENU.Keys.Line2:
                    //Debug.Print("Line2");
                    if (line[2].menuTag != "Blank")
                    {
                        currentMenu = line[2];
                        selectMenu();
                    }
                    break;

                case MENU.Keys.Line3:
                    //Debug.Print("Line3");
                    if (line[3].menuTag != "Blank")
                    {
                        currentMenu = line[3];
                        selectMenu();
                    }
                    break;

                default:
                    break;
            }
        }

The other thing is that they're not always switches either and take on even more roles during sub functions....

 

I could work around this with minimal code reworking if there was an easy way to pause a thread and wait for an interrupt to continue. So instead of using a keytimer to scan the inputs it just waited for an interrupt to trigger that line. In other parts of code i have a wait/continue button thread for when people are reading an output on the display. On another occasion the line 4 button "becomes" next/continue. So i need to stop listening for the interrupt to act as line 4 for a moment and have it act as a continue button.

 

I just used too scan it like this.

public bool Wait(int timeout = 15, int SW = 1)
        {
            bool Wake_SW;
            int newtimeout = timeout * 10;
            Wake_SW = Multi.Switch[SW].Read();

            int x = 0;

            for (int Counter = 0; Counter < newtimeout; ++Counter)
            {
                Wake_SW = Multi.Switch[SW].Read();
                Thread.Sleep(100);
                x++;

                if (Wake_SW == !true)
                {
                    return true;
                }
            }

            if (x > 0)
            {
                return false;
            }
            else if (Wake_SW == !true)
            {
                return false;
            }
            else
            {
                return true;
            }
        }

Now I guess i can fix this all up with some sort of a wait for interrupt but dispose previous interrupt function type of thingy...... Any suggestions. But if you can't tell, I'm a real noob so you going to have to spell it out to me with a little patience. I've just picked this up again after 6 months and am feeling very rusty.

 

Thanks in advance.



#2 Spiked

Spiked

    Advanced Member

  • Members
  • PipPipPip
  • 129 posts

Posted 30 September 2014 - 02:14 AM

Hopefully, you have some UI experiences with message pump applications, like windows.  Think mouse buttons. You get an event mouse down, a different event for mouse move, and yet another for mouse up.  

 

You can tie any digital pin as an interrupt with the netduino (to my knowledge) as its not really an interrupt but a flag to the low level code to poll the pin and post an event at level change, and this appears to your code as an interrupt event and the actual event is time stamped with the actual poll time, so while your code may not get it exactly when it happens, you do get a good timestamp of when it happened. At least this is my understanding, and so far has worked out.

 

With that in mind you do the same, post as a mouse/button down event up a level, and in your code check to see if that event occurred. Dont forget to indicate that the event has been 'handled' if appropriate, and dont forget to take the proper action, for mouse/button down and mouse/button up.

 

Quick look at your above code; make Wake_SW global. in a level high interrupt, set it to true. When you handle it in code do not forget to reset it to false. Also if you are not doing both level down and up, you need to re-enable the interrupt each time it occurs.

 

If I have any of this wrong, please someone jump in, but I think this is the way it works.  I'm using 2 motors, 2 interrupt pins per motor (ie 4 interrupts) and I think it is doing ok reading encoders (now, after a lot of experiments). I get some jitter determining speed, but I think that is just frequencies not being perfectly aligned (motor encoders vs firmare polling), so I smooth them out. At least until somebody explains otherwise to me. To my knowledge, I am not losing any interrupts, just getting the velocity jitter, when the motion is smooth.



#3 stotech

stotech

    Advanced Member

  • Members
  • PipPipPip
  • 143 posts
  • LocationAustralia

Posted 30 September 2014 - 02:49 AM

Thanks for trying spiked but you've under estimated the noob-ed-ness of my understanding. I have only a little experience at just winging it. Can i make a little object/class/method(don't even know the lingo yet) that says "wait for interrupt on port xyz before continuing." without looping over and checking for a flag? Example's much appreciated.

 

Thanks



#4 Spiked

Spiked

    Advanced Member

  • Members
  • PipPipPip
  • 129 posts

Posted 30 September 2014 - 06:36 AM

You are pretty close to that now. The wait function you posted, is close. But instead of (reading and) setting wait_Flag in this routine, set it in an interrupt routine as described in my last post.

 

Yes, you can make a nice little class out of it, but you'll have to make arrangements for multiple wait flags, one for each pin/switch maybe. You will have to do a little exploring on your own, you learn so much more that way.

 

If you do get stuck, follow up with questions. BTW, looking at your code, you are doing fine for a newb :)



#5 CW2

CW2

    Advanced Member

  • Members
  • PipPipPip
  • 1592 posts
  • LocationCzech Republic

Posted 30 September 2014 - 06:38 AM

...it's not really an interrupt but a flag to the low level code to poll the pin and post an event at level change

 

Nope, there is no low level polling, but real hardware interrupts - when an interrupt condition is detected by pin logic a native interrupt handler is called, which creates event with timestamp and posts it into internal queue for the CLR to execute managed callbacks (event handlers).

 

@Grant: Have a look at InterruptPort class and its OnInterrupt event, you can re-use one event for multiple pins and then differentiate using its first parameter.



#6 Spiked

Spiked

    Advanced Member

  • Members
  • PipPipPip
  • 129 posts

Posted 30 September 2014 - 08:09 AM

I didn't know that CW2. Somewhere I read the timing could be off by 4us, so I kind of guessed at the rest.  I guess it also depends on what you consider low level code, as pin logic certainly could be considered really low level code, but you're right it was not what I was thinking.

 

Is there any reason to re-use an event handler other than to save memory, and we are talking about a very tiny routine as discussed.



#7 stotech

stotech

    Advanced Member

  • Members
  • PipPipPip
  • 143 posts
  • LocationAustralia

Posted 30 September 2014 - 09:51 AM

I'm starting to wonder myself, if it is worth the trouble. I really thought there would be a simple way to say "pause for interrupt pin x" ??? But I'd feel really dumb not using them them now as I went and assigned them to the button's on the hardware.



#8 Ronald

Ronald

    New Member

  • Members
  • Pip
  • 5 posts
  • LocationShrewsbury, England

Posted 30 September 2014 - 11:41 AM

Interrupt event handlers are the best way to go, in my view. May I suggest you take a look at the Advanced Tutorials "event handlers   the button revisited" video. It provides an excellent example of using an interrupt event handler.  



#9 CW2

CW2

    Advanced Member

  • Members
  • PipPipPip
  • 1592 posts
  • LocationCzech Republic

Posted 30 September 2014 - 12:19 PM

I didn't know that CW2. Somewhere I read the timing could be off by 4us

 
Well, it certainly takes some time for the native interrupt handler to execute, but I don't have any numbers  :(
 

I guess it also depends on what you consider low level code, as pin logic certainly could be considered really low level code

 
I guess in principle yes, but there is no code executed in the similar way like CPU executes its instructions. The pin logic input circuitry for edge detection is just a few logic gates (of course in real microcontroller it is a little bit more complex) and the interrupts are handled in a specialized unit (for ARM it is named Nested Vectored Interrupt Controller, NVIC) - only after validation, prioritization etc. NVIC notifies CPU, which then loads an interrupt handler address from vector table and starts executing the instructions. If you are interested in more details, have a look for example at the following document.
 

Is there any reason to re-use an event handler other than to save memory, and we are talking about a very tiny routine as discussed.

 
Personally, I sometimes re-use event handler when I need to handle multiple buttons, like this
...
InterruptPort button1 = new InterruptPort(Pins.GPIO_PIN_D0, ...);
InterruptPort button2 = new InterruptPort(Pins.GPIO_PIN_D1, ...);
InterruptPort button3 = new InterruptPort(Pins.GPIO_PIN_D2, ...);
...
button1.OnInterrupt += button_Interrupt;
button2.OnInterrupt += button_Interrupt;
button3.OnInterrupt += button_Interrupt;
...
void button_Interrupt(uint data1, uint state, DateTime time)
{
  var pin = (Cpu.Pin)data1;
  var on = (state != 0);

  switch(pin)
  {
    case Pins.GPIO_PIN_D0:
      ...
      break;
    case Pins.GPIO_PIN_D1:
      ...
      break;
    ...
  }
  // Or use data1 as array index, collection key etc.
}
It also makes diagnostic/troubleshooting easier in certain cases.

#10 Spiked

Spiked

    Advanced Member

  • Members
  • PipPipPip
  • 129 posts

Posted 30 September 2014 - 02:53 PM

Perfect, thanks for that. I have a feel for the grey line between hardware and software having worked some with the microcode interpreter that makes an IBM system 370 run (the good old days). I'm sure that idea has carried forward, but I am not intimately familiar with it anymore, like I was.

 

Great explanation on why on the single interrupt handler. My code currently wraps threads in objects, so I sort of accomplish the same goal (re-entrant single code instance), albeit a slightly different path to get there (the encoder stuff).



#11 stotech

stotech

    Advanced Member

  • Members
  • PipPipPip
  • 143 posts
  • LocationAustralia

Posted 30 September 2014 - 11:50 PM

Cool thanks for that example. I'm going to try and make something of it and try too apply it. Don't go anywhere though. I'll be back here if i get stuck.

 

Thanks Again



#12 stotech

stotech

    Advanced Member

  • Members
  • PipPipPip
  • 143 posts
  • LocationAustralia

Posted 01 October 2014 - 03:54 AM

OK Guru's, What about this....

    class Interrupts
    {
        #region interupt's

        static InterruptPort SW_0;
        static InterruptPort SW_1;
        static InterruptPort SW_2;
        static InterruptPort SW_3;
        static InterruptPort SW_4;
        static InterruptPort SW_5;

        #endregion

        public static void menu_return()
        {
            Establish_Buttons();

            //need help here............
            Thread.CurrentThread.Join();
        }

        public static void Establish_Buttons()
        {
            SW_0 = new InterruptPort(STM32Provider.Pins.PG10, true, Port.ResistorMode.Disabled, Port.InterruptMode.InterruptEdgeLow);
            SW_1 = new InterruptPort(STM32Provider.Pins.PG11, true, Port.ResistorMode.Disabled, Port.InterruptMode.InterruptEdgeLow);
            SW_2 = new InterruptPort(STM32Provider.Pins.PG12, true, Port.ResistorMode.Disabled, Port.InterruptMode.InterruptEdgeLow);
            SW_3 = new InterruptPort(STM32Provider.Pins.PG13, true, Port.ResistorMode.Disabled, Port.InterruptMode.InterruptEdgeLow);
            SW_4 = new InterruptPort(STM32Provider.Pins.PG14, true, Port.ResistorMode.Disabled, Port.InterruptMode.InterruptEdgeLow);
            SW_5 = new InterruptPort(STM32Provider.Pins.PG15, true, Port.ResistorMode.Disabled, Port.InterruptMode.InterruptEdgeLow);

            SW_0.OnInterrupt += SW_OnInterrupt;
            SW_1.OnInterrupt += SW_OnInterrupt;
            SW_2.OnInterrupt += SW_OnInterrupt;
            SW_3.OnInterrupt += SW_OnInterrupt;
            SW_4.OnInterrupt += SW_OnInterrupt;
            SW_5.OnInterrupt += SW_OnInterrupt;
        }

        static void SW_OnInterrupt(uint data1, uint data2, DateTime time)
        {
            Disable_Buttons();

            var pin = (Cpu.Pin)data1;
            var on = (state != 0);

            switch(pin)
            {
                case Pins.GPIO_PIN_D0:
                //execute menu sw variable's in which interupts may be used
                menu_return()
                break;
                case Pins.GPIO_PIN_D1:
                //execute menu sw variable's in which interupts may be used
                menu_return()
                break;
            }
        }

        static void Disable_Buttons()
        {
            SW_0.Dispose();
            SW_1.Dispose();
            SW_2.Dispose();
            SW_3.Dispose();
            SW_4.Dispose();
            SW_5.Dispose();
        }
    }

So i think this would dispose the interrupts, go and carry out a menu function (which might reassign and use those pins, dispose it when done) and then return and enable the interrupts for the menu again i think. Except i'm a little confused about what to do with the thread. If i do nothing and the thread exit's, do the interrupts stay active?

 

I'm not able to test at the minute, so i'm not sure if this would actually work or not. I might be right. Not sure about thread join though?



#13 CW2

CW2

    Advanced Member

  • Members
  • PipPipPip
  • 1592 posts
  • LocationCzech Republic

Posted 01 October 2014 - 09:26 AM

I have to re-read the original post to understand what you are trying to do, but a few notes regarding the code:

  • You should specify pull-up resistor in the interrupt constructor, to prevent spurious events (use pull-up for low/falling edge, pull-down for high/rising edge),
  • If you really must disable interrupts, it is easier to set InterruptMode to InterruptNone. Disposing is rather costly and events can be missed (no event handler when disposed). Alternatively, you can use boolean variable checked in the interrupt handler
static bool buttonsDisabled = false;

static void DisableButtons()
{
  buttonsDisabled = true;
}

static void SW_OnInterrupt(uint data1, uint data2, DateTime time)
{
  if(buttonsDisabled)
    return;
  ...
}

Also, I would recommend you not to use threads initially - perhaps create a new project just to play with the button interrupts, experiment with different scenarios. Once you understand how it works, continue adding the functionality, implement threads only when really necessary/beneficial. In many cases, it is enough to use "Super-Loop" - the core/processing code is wrapped in while(true) loop implemented in Main() method, occasionally checking various state variables set by interrupt handlers.



#14 stotech

stotech

    Advanced Member

  • Members
  • PipPipPip
  • 143 posts
  • LocationAustralia

Posted 01 October 2014 - 09:33 AM

Thanks for all the help. I really really appreciate it. I'm sure if I get a few days to poke around with it I can figure it out based on what you've given me so far.

 

I should've added that I've got external 10k pull-up's on the switches.

 

Thanks Again. 







0 user(s) are reading this topic

0 members, 0 guests, 0 anonymous users

home    hardware    projects    downloads    community    where to buy    contact Copyright © 2016 Wilderness Labs Inc.  |  Legal   |   CC BY-SA
This webpage is licensed under a Creative Commons Attribution-ShareAlike License.