GPIO Programming and Interrupts


64 posts   Page 3 of 3   1, 2, 3
by Daverj » Wed Jul 25, 2012 10:32 pm
rurwin wrote:
Daverj wrote: not requiring the program to stop and wait for an interrupt.

That would be either a contradiction in terms or how every computer handles interrupts.


Well of course physically the program does stop while the ISR is executed. What I meant was not adding some call in the program that makes the program stop and wait for the interrupt to be executed, as was described earlier in the thread. That's only useful if the program is doing something that requires waiting for an event, such as processing a stream, or waiting for input.

rurwin wrote:The encoders I have experience of only have two outputs: A and B, and they only require one interrupt.

Hardware interrupts on general purpose pins usually involve a separate interrupt per pin. But those interrupts in this case can be serviced by the same interrupt service routine.

Many rotary encoders include a third pin for "Index" which allows you to reset your position counter based on a known physical position of the encoder, allowing an incremental encoder to be used like an absolute encoder.

And yes, a rotary encoder could generate a lot of interrupts if it is high resolution and turned quickly. So clearly there would be practical limits in what the Pi could handle. I suspect with very small ISRs written in assembler they could execute in under 100ns and have very little impact on the rest of the system.

gordon@drogon.net wrote:Think of it this way: You split your program into 2 threads. These 2 threads then run in parallel. One of these 2 waits for the interrupt (it waits in the kernel, it's not busy polling using CPU) When the interrupt happens, the other thread is stopped and the program that's waiting for the interrupt starts. When it's done that program goes back to sleep, waiting on the next interrupt, and the main program carries on as in nothing has happened. It shouldn't miss the interrupt, but may be delayed if there's a higher priority thread/program active at the time.


I haven't had much time to play with the Pi yet, but when I do I'll give your method a try.

It sounds like your method isn't using a real interrupt service routine created by the user, but instead is getting Linux to switch tasks based on the interrupt. Still a true interrupt, but with a lot more delay and overhead before the interrupt is serviced.

What I had in mind would, I think, be a lot less overhead for Linux. I was thinking of creating a very small interrupt service routine in assembler, so it could run very quickly, and stick it's addresses into the CPU interrupt vector table. Then just have a common variable that it and the main program could access.

I also don't see the need for the mutex in your example. If the variable is something that can be accessed in a single instruction cycle (ie: a 16 bit unsigned int can be read or written in a single instruction, even using the ARM thumb instruction set) then reading it from the main program will always give a valid value, even if the interrupt happens one cycle before or after the variable is read. For reading arrays or larger variables, or if the main program had to modify the variable, then I could see the need.
Posts: 28
Joined: Tue Mar 06, 2012 2:23 am
by gordon@drogon.net » Wed Jul 25, 2012 10:47 pm
Daverj wrote:
What I had in mind would, I think, be a lot less overhead for Linux. I was thinking of creating a very small interrupt service routine in assembler, so it could run very quickly, and stick it's addresses into the CPU interrupt vector table. Then just have a common variable that it and the main program could access.


I suspect it might not be as easy as that, but I'm no Linux kernel expert. you'd need to work out some way to share the memory between your interrupt routine and the program running in user-land, although it may be that Linux already had that mechanism - I don't know.

Daverj wrote:I also don't see the need for the mutex in your example. If the variable is something that can be accessed in a single instruction cycle (ie: a 16 bit unsigned int can be read or written in a single instruction, even using the ARM thumb instruction set) then reading it from the main program will always give a valid value, even if the interrupt happens one cycle before or after the variable is read. For reading arrays or larger variables, or if the main program had to modify the variable, then I could see the need.


It was just a demo.

-Gordon
--
Gordons projects: https://projects.drogon.net/
User avatar
Posts: 1530
Joined: Tue Feb 07, 2012 2:14 pm
Location: Devon, UK
by Daverj » Wed Jul 25, 2012 11:25 pm
I've been writing C for about 30 years, and ISRs for a few years before that, but I have very little experience with Linux. And especially with Linux running on something small like the ARM. So I'm mostly looking for what's possible vs what's practical, and figure out the limits of what I can get the Pi to do. I could certainly add a PIC to deal with the shaft encoder and then feed the variable to the Pi via SPI, but part of the goal is always to add as little extra hardware as possible.

I can certainly see using that handshaking in a demo, since people will start from there and go in all directions. I just didn't see the need in that specific application. (though again, I have very little Linux experience). I wasn't trying to criticize the way you did it as much as trying to understand if there was something I didn't know about which made it necessary.
Posts: 28
Joined: Tue Mar 06, 2012 2:23 am
by pygmy_giant » Thu Jul 26, 2012 12:03 am
It horses for courses - the best solution is whats best for you (if it works).

I also am trying to avoid unnecessary hardware and looking at encoders and interupts for my balancing robot. My brain is of average size and I like to maintain a simple outlook on life, so the thought of multiple threads waking up, butting in and going back to sleep with unpredictable lag makes me queezy.

My simplistic brute force solution is currently to have one infinate loop gathering data from sensors as required, processing this and then making decisions before looping again.

I know this isn't very ingenious but I'm hoping that by doing this on the bare metal it will be quick enough.

I intend to compile a binary (probably from C++) and link it to DexOs' boot loader and so avoid linux altogether.

I think I will avoid interupts unless this approach proves to be too slow, in which case I will look into it again.

I can see the argument for threading and interrupts as i admit my bot could struggle if it stops to check its location in a map and then falls over because this takes an unpredictably long time.

I still think I will try the simple approach first just incase it works. I'm not convinced the threading and interrupt overhead / disruption is worth it in time critical applications - it seems a bit of a luxury aimed at allowing easily maintainable code.

Am I being closed minded and dismissive?
Ostendo ignarus addo scientia.
Posts: 1569
Joined: Sun Mar 04, 2012 12:49 am
by rurwin » Thu Jul 26, 2012 6:28 am
pygmy_giant wrote:Am I being closed minded and dismissive?
No, but you're not seeing the whole picture.

If you run your program at normal priority it will get interrupted by other processes. You will get stretches of time where your fast I/O is not being looked at.

If you run your program at real-time priority, then nothing else will get a look in and the machine will hang.

But if you put your fast I/O in one thread running at real-time priority and either wait for interrupts or sleep now and again for a few ms, and your other stuff in a normal priority thread, then everything will work fine.
User avatar
Forum Moderator
Forum Moderator
Posts: 2913
Joined: Mon Jan 09, 2012 3:16 pm
by pygmy_giant » Thu Jul 26, 2012 4:01 pm
but If I run my single program without linux on the bare metal as a kernal isn't there only one priority - mine!?
Ostendo ignarus addo scientia.
Posts: 1569
Joined: Sun Mar 04, 2012 12:49 am
by Daverj » Thu Jul 26, 2012 4:38 pm
pygmy_giant wrote:but If I run my single program without linux on the bare metal as a kernal isn't there only one priority - mine!?


Yes, but depending on what the slowest time it takes to get once around your loop, some of your hardware might not be sensed and controlled fast enough to keep it functioning.

Even in a bare metal single process system, interrupts can be the difference between it working or not. You also can write a sort of fake muti-tasking system by breaking up all of the different tasks that are needed into tiny steps (states) and each time around the loop only do one little piece of each of them. The faster you can get the worst case time around the loop, the closer it will feel to real time.

But keeping track of everything that way can be complicated in a system with a lot going on, while doing it using an OS with multi-tasking can make several things happen seemingly at the same time. And interrupts, even the kind described above, can speed up those time critical parts.
Posts: 28
Joined: Tue Mar 06, 2012 2:23 am
by pygmy_giant » Thu Jul 26, 2012 5:49 pm
thanks - I think I understand that.

I guess a continuous loop could look like this:

1) read clock and store value
2) check gyro / accellerometer / wheel encoders, then do balancing
3) work out next few priorities and add to task cue if tasks less than 3
4) read clock and check against stored value to see how much of 7ms loop time is left
5) do next task in que if there is one and time to do it otherwise goto 1
6) goto 4

Step 3 would be the ai as that would be where decisions are made - tasks would be scheduled according to decisions based upon stored sensor readings and flags.

The tasks in step 5 would either be 'actions' such as display a message, 'observations' such as read a sensor and store its value or 'processes' such as manipulate data according to algorithms.

My approach would not have threads as such but would have prioraties, time management and quantised tasks.
Ostendo ignarus addo scientia.
Posts: 1569
Joined: Sun Mar 04, 2012 12:49 am
by rurwin » Fri Jul 27, 2012 7:18 am
Daverj wrote:I also don't see the need for the mutex in your example. If the variable is something that can be accessed in a single instruction cycle (ie: a 16 bit unsigned int can be read or written in a single instruction, even using the ARM thumb instruction set) then reading it from the main program will always give a valid value, even if the interrupt happens one cycle before or after the variable is read. For reading arrays or larger variables, or if the main program had to modify the variable, then I could see the need.

You may be right in the case of the RaspPi, but even then I would check the ARM specification, because processors have been known to be able to interrupt in the middle of an instruction. But in a multiprocessor machine such as most desktop systems, then even a non-aligned multi-byte write can be "interrupted" by a task running on another processor. The point is that if you don't use a mutex, then you need to understand the machine you are using at a very low level. You also need to program in assembler; C may be designed to produce optimal code such as single instruction increment, but it doesn't always work that way and you can't depend on it.

The other difficulty in using a mutex with a real interrupt handler, is what do you do if the background task has the mutex locked when your interrupt happens? That wont lock you out with Linux poll() of course, but it may increase latency. My personal preference is to have variables that only the interrupt routine writes to and others that only the background task writes to. That way you don't need a mutex, but you still have to ensure that all writes are atomic. The easiest way to do that is to make them bytes, then you can program everything in C, (but be sure to mark them "volatile".)
User avatar
Forum Moderator
Forum Moderator
Posts: 2913
Joined: Mon Jan 09, 2012 3:16 pm
by throstur62 » Fri Jul 27, 2012 8:30 am
Gordon, I tried to compile your wfi.c example such:
cc -o wfi.exe wfi.c -I/usr/local/include -L/usr/local/lib -lwiringPi

But got this error message:
/usr/local/lib/libwiringPi.a(piThread.o): In function `piThreadCreate':
piThread.c:(.text+0x18): undefined reference to `pthread_create'
collect2: ld returned 1 exit status

I have been in Windows C/C++ programming for 25 years, but a Linux newby, so this looks a little like Chineese to me. Can you tell me what this error message linking indicates?
User avatar
Posts: 7
Joined: Thu Jul 26, 2012 1:34 pm
by khh » Fri Jul 27, 2012 9:04 am
throstur62 wrote:Gordon, I tried to compile your wfi.c example such:
cc -o wfi.exe wfi.c -I/usr/local/include -L/usr/local/lib -lwiringPi

But got this error message:
/usr/local/lib/libwiringPi.a(piThread.o): In function `piThreadCreate':
piThread.c:(.text+0x18): undefined reference to `pthread_create'
collect2: ld returned 1 exit status

I have been in Windows C/C++ programming for 25 years, but a Linux newby, so this looks a little like Chineese to me. Can you tell me what this error message linking indicates?

pThreads (POSIX Threads) is the most common threading API on POSIX operating systems. Try linking it to pThreads by adding a -lpthread switch.

Also in linux executables normally don't have any extension, object files use .o, shared libraries use .so and static libraries .a
Posts: 49
Joined: Thu Jul 26, 2012 12:16 am
by throstur62 » Fri Jul 27, 2012 10:08 am
Thanks for the quick answare. Yes this sloved my linking broblem. I shall try to get used to not using .exe :-)
So for those that want the valid compilation command it is:
cc -o wfi wfi.c -L/usr/local/lib -lwiringPi -lpthread
It works grate.
One question regarding the code I do not understand fully:
When you set the GPIOS from the bash (command promt) you use the -g option to use BCM-numbering for exampel:
$ gpio -g mode 17 out
refering to GPIO17 on the Broadcom chip.
Hoever in the code of wfi.c I find those lines:
system ("gpio edge 0 falling") ;
system ("gpio export 17 out") ;
system ("gpio export 18 out") ;
Notice that there is no "-g" here. I find out that this is also the case described at http://elinux.org/RPi_Low-level_peripherals
So from this it seems to be that I should use the "-g" when typing in directly from the prompt but no "-g" when doing it from a script or program (that is to day when I want do use the BCM pin numbers).
This is confusing. Why is it such?


cc -o wfi wfi.c -L/usr/local/lib -lwiringPi -lpthread
User avatar
Posts: 7
Joined: Thu Jul 26, 2012 1:34 pm
by gordon@drogon.net » Sat Jul 28, 2012 7:39 pm
throstur62 wrote:Thanks for the quick answare. Yes this sloved my linking broblem. I shall try to get used to not using .exe :-)
So for those that want the valid compilation command it is:
cc -o wfi wfi.c -L/usr/local/lib -lwiringPi -lpthread
It works grate.
One question regarding the code I do not understand fully:
When you set the GPIOS from the bash (command promt) you use the -g option to use BCM-numbering for exampel:
$ gpio -g mode 17 out
refering to GPIO17 on the Broadcom chip.
Hoever in the code of wfi.c I find those lines:
system ("gpio edge 0 falling") ;
system ("gpio export 17 out") ;
system ("gpio export 18 out") ;
Notice that there is no "-g" here. I find out that this is also the case described at http://elinux.org/RPi_Low-level_peripherals
So from this it seems to be that I should use the "-g" when typing in directly from the prompt but no "-g" when doing it from a script or program (that is to day when I want do use the BCM pin numbers).
This is confusing. Why is it such?


When I first did wiringPi, I decided to implement a scheme like the Arduino where they abstract the real underlying hardware into a simple "pin" number. So e.g. Port C, bit 5 on an ATmega168 is "pin 13"... it was designed to make the Arduino+wiring system easy to use for newcomers...

Fast wind forward to the Pi and I implemented a similar scheme... However it was obvious that people really needed/wanted/etc. to use the native broadcom pins numbering, so I did re-spin of wiringPi to accommodate it, but I needed to maintain compatability with older code, so in your using it from C, then you call wiringPiSetup () for the original way, and wiringPiSetupGpio() for the native GPIO way.

Then came the need to not use the GPIO natively, but via the /sys/class/gpio interface. This uses the same pin numbers as the native GPIO, so not that hard to implment - just call wiringPiSetupSys()...

Then there is the GPIO program - it works 3 ways - the original uses my wiringPi Pins, then the -g flag allows the use of native GPIO numbers, however because the sys interface (which with the gpio program always uses native GPIO numbers) was then added, and really, you will always use the native numbering scheme with the sys interface, I got lazy with command-line parsing and just fixed it to use the native GPIO numbers.

So:

gpio mode 0 out

Sets wiringPi pin0 which is native GPIO pin 17.

gpio -g mode 17 out

has the same effect on the same physical pin.

gpio export 17 out

exports GPIO pin 17 (ie. the same one again) as an output, but makes it avalable via the sys interface, so programs can then use it without needing to run as root/sudo.

So in summary, when using the sys interface (ie. edge, export, unexport), then the pin numbers are always native GPIOs.


And always check the Makefiles for how the programs are compiled, although if you're not a Makefile expert (who is!) then it can sometimes be a little daunting to try to parse them.

I'm almost tempted to swap it round for the gpio program - ie. drop the -g flag, and add in a -w flag to refer to the original wiringPi pins, because I suspect most people are using the native pins, however I've no real way of telling...


-Gordon
--
Gordons projects: https://projects.drogon.net/
User avatar
Posts: 1530
Joined: Tue Feb 07, 2012 2:14 pm
Location: Devon, UK
by xtramural » Sat Jul 28, 2012 9:58 pm
gordon@drogon.net wrote:I'm almost tempted to swap it round for the gpio program - ie. drop the -g flag, and add in a -w flag to refer to the original wiringPi pins, because I suspect most people are using the native pins, however I've no real way of telling...

Why not make it a make/install option for any new release of wiringPi so that those people that use the original approach can preserve the behaviour if they so wish and any new users of your code can go native without further ado?
Posts: 104
Joined: Thu Dec 29, 2011 11:16 pm
Location: Scotland