EBC Exercise 11 gpio Polling and Interrupts
In the previous exercise (EBC Exercise 02 Flashing an LED) we saw how to interact with the general purpose I/O pins via the command shell and the sysfs files. These are rather easy ways to work with gpio; however they tend to be slow and require a lot of the CPU. In this exercise we'll explore using sysfs via C. We'll also see how using interrupts can greatly reduce the CPU usage and increase our max output speed.
gpio via C and sysfs
/sys/class/gpio/gpio130/value is just a file, we can read and write it from a C program just as easily as from the shell. Here is a nice example of how it's done. I've cleaned it up a bit and put a copy called togglegpio.c here. Get a copy and look it over. It' presently hardwired to use gpio130. Later you will get to make it work with any port.
- Compile and run it. Does the correct waveform appear on the oscilloscope?
- Use htop to measure the CPU usage.
- Try different periods. Make a chart of the input period vs. the measured period vs. CPU usage.
This program is really interrupt based. When the usleep command is run, the process suspends until the correct time has elapsed. Other processes are allowed to run. When the time is up the CPU is interrupted and our process is allowed to continue.
gpio via Interrupts
Next we want to write some code that will read one gpio pin and copy its value to another. We could modify togglegpio.c to continually read one pin and write it to another, but that would wither be slow at responding to changes (a usleep is used between reads), or it would take all the CPU time (always reading in case a change was made).
A better approach is to let the OS tell you when the input has changed. In a traditional microprocessor one would set up the input pin to interrupt the processor when its input value has changed. We'll do something similar here.
The folks at RidgeRun have a nice example of how this is done. (I think you will need to change line 165 to
fd = open(buf, O_RDONLY); for it to work. The RidgeRun folks may need to be notified too -- Garbear). (Thanks for the bug report. I've fixed it and passed on the info to RidgeRun. It's not easy giving feedback from their site. Fortunately I've met Todd before and had his email address.)
Look over the example. The first part should be familiar. The new stuff starts here. It describes how to use the poll() command to wait until the gpio pin has changed. Your process will block (i.e. let other processes run) while poll() waits for an interupt to occur.
I've put a modified copy of gpio-int-test.c here. Get it. Study it. Compile and run it. You'll need to setup an input signal. gpio4 is the User button. Try it. Does the program respond correctly?
- Modify togglegpio.c to take the gpio number.
- Clean things up a bit and use the structure of gpio-int-test.c.
- Presently one parameter specifies both the on and off time. Modify the code so the on and off times are controlled separately.
- Add in interrupt handler (see gpio-int-test.c) to trap ctrl-C and close things properly.
- What's the highest frequency you can see on the 'scope?
- Modify gpio-init-test.c to count the number of times the User button has been pressed. Set edge to only count releases.
- Copy gpio-init-test.c to gpioThru.c and modify it to copy the value of one port to another. You'll have to add code to open a second gpio port for writing (check gpio_set_dir).
- gpio 131 is attached to pin 19 of the Main Extension Header on the Beagle. Attach a function generator to this pin. Be sure to set the HiLevel to 1.8V and the LoLevel to 0. Does the output track the input?
- What's the highest frequency the output will track the input? What's the CPU usage?
- What's the delay from when the input changes to when the output changes?