Whether your application needs fast access or specialized access from GPIO, a C/C++ program is the most convenient way to go. Python programs likewise can have direct access with the help of a module.
This chapter looks at how to access the GPIO ports directly from within a program, starting with the unfinished business of using the uevent file, to detect input GPIO changes with the help of interrupts behind the scenes.
Edge Events
The previous chapter introduced the uevents pseudo file that the GPIO driver provides. There it was explained that you needed to use one of the system calls poll(2) or select(2) to take advantage of this notification. Here I’ll illustrate the use of poll(2), since it is the preferred system call of the two.
The open file descriptor is placed into the fd member, while the events of interest are saved to member events. The structure member revents is populated by the system call, which is available upon return.
The gpio_poll() function, invoking the poll(2) system call
The first argument supplies the address of the first structure entry (equivalent to &polls[0]). The second argument indicates how many entries apply to this call (there is only one). The last argument is a timeout parameter in milliseconds, with the negative value meaning to block forever.
to see if there was activity for this file descriptor. In this program we don’t test it because only one file descriptor is provided (it’s the only file descriptor that can return activity). But if you were testing for two or more GPIOs, this test would be required.
When the returned value from poll(2) is zero, it simply means that the timeout has occurred. In this program, no timeout is used, so this cannot happen.
If the returned value is -1, then the system call has returned because of an error. There is one special error code, EINTR, which will be explained shortly.
For normal read data, the event macro name to use is POLLIN. For the uevent pseudo file, the event macro name is POLLPRI, indicating urgent data to be read. The data to be read is indeed urgent because the state of the GPIO port could change by the time you read the value pseudo file. So if you’re hoping to catch rising events, don’t be surprised if you sometimes read back a zero. When that has happened, the rising event has come and gone by the time that the GPIO state was read.
EINTR Error
In order for the program to notice that the variable has changed to non-zero, the kernel returns with rc=-1 to indicate an error, and sets the errno=EINTR. The code EINTR simply means that the system call was interrupted and should be retried. In the code presented, line 137 tests to see if that variable was set to non-zero. If it was, the function immediately returns. Otherwise, the while loop in line 139 keeps the system call retried in line 136.
Reading uevent
Line 143 effectively performs a rewind on the file descriptor before it reads it in line 144. This informs the driver to make its event data available for the upcoming read. Line 146 simply puts a null byte at the end of the read data, so that sscanf(3) can use it. Since we are expecting a 0 or 1 in text form, this is converted into the integer value n in line 148 and then returned.
Demonstration
This program will not require you to sudo, because it sets the evinput executable to setuid root. On secure systems, you may want to review that.
The expected value read is a 1 after a rising edge. However, notice that one zero snuck in there, which is a reminder that contact bounce and timing plays a role. The first value displayed by this program is always the initial state of the GPIO.
Multiple GPIO
The evinput.c program has been kept simple for illustration purposes. But the usefulness of the edge detection may have you applying it to multple GPIO ports at once. The beauty of the poll(2) approach is that your application will not waste CPU cycles waiting for an event to occur. Instead the GPIO interrupt will inform the kernel of the change when it occurs, and thus inform the uevent driver. This in turn will inform poll(2) when performed on the pseudo file.
To expand the demo code to multiple GPIOs, you will first need to open multiple uevent pseudo files after putting the GPIO into the correct configuration. Then you will need to expand the array polls[] to include the number of GPIOs of interest (line 128). Then initialize each entry as shown in lines 132 and 133.
The second argument to the poll(2) call in line 136 needs to match the number of initialized array elements. If you are monitoring five GPIOs, then argument two needs to be the value 5.
In this manner, your application can very efficiently monitor several GPIO inputs for changes. Your code must however be able to cope with contact bounce. Some ICs like the PCF8574 I2C GPIO expander, sport an INT pin that can be monitored using this approach.
Direct Register Access
It is sometimes necessary for a user mode program to have direct access to the GPIO registers for performance or other reasons. This requires root access to control user access and because if done incorrectly, can crash your system. A crash is highly undesirable because it can cause loss of files.
The introduction of new Raspberry Pi models has added the challenge of dealing with different hardware platforms. With the original Raspberry Pi Model B and subsequent Model A, there was one fixed hardware offset to the peripheral registers. However, that has changed, and we now need to calculate the correct register address depending upon the hardware model involved.
Peripheral Base Address
- 1.
Determine base address of our register set
- 2.
Need to map physical memory into our virtual address space
Determining the peripheral base address
- 1.
Open the pseudo file (line 321).
- 2.
Read the first 8 bytes into character array buf (line 323).
- 3.
Once read, the file descriptor can be closed (line 325).
- 4.
Piece together the address in line 326.
- 5.
If step 1 fails, assume the value of macro BCM2708_PERI_BASE (which is 0x3F00000).
Mapping Memory
Mapping physical memory
- 1.
First memory is accessed by opening /dev/mem, for read and write (line 278). This step requires root access to protect the integrity of the system.
- 2.Once that file is open, the mmap(2) system call is used to map it into the caller’s virtual memory (lines 282 to 289).
- a.
The first argument of the call is NULL, to specify that any virtual memory address is acceptable. This address can be specified, but the call will fail if the kernel finds it unacceptable.
- b.
Argument two is the number of bytes to map for this region. In our demo program, this is set to the kernel’s page size. It needs to be a multiple of the page size.
- c.
Argument three indicates that we want to read and write the mapped memory. If you only want to query registers, macro PROT_WRITE can be dropped.
- d.
Argument four is MAP_SHARED allowing our calling program to share with any other processes on the system that might be accessing the same region.
- e.
The fifth argument is the file descriptor that we have open.
- f.
The last argument is the starting offset of physical memory that we wish to have access to.
- a.
- 3.
If the mmap(2) call fails for any reason, the return value will be a long negative one. The value errno will reflect the reason why (lines 291 to 296).
- 4.
Otherwise, the file can be closed (line 298) since the memory access has already been granted. The virtual memory address is returned in 299.
Register Access
- 1.
The register address (saved to gpiolev, line 232).
- 2.
The bit shift needed (saved to variable shift, line 227).
- 3.
From the required register to be accessed (GPIO_GPLEV0, line 232).
C function, gpio_read() to read a GPIO input bit
Line 234 then accesses the register containing the GPIO bit of interest and returns it to the caller.
Writing GPIO registers by writing to the register address
The one difference between read and write. however, is that the Pi has different registers to set GPIO bits (line 249) and another to clear them (line 252).
Demonstration Program
All invocations require the specification of the -g option to provide the GPIO number to operate upon. Option -v can be added to provide additional output.
GPIO Input
Your session output might show some contact bounce, so don’t expect all transitions to be consistently ones alternating with zeros.
GPIO Output
In this session, the verbose option was added for visual confirmation.
Drive, Hysteresis, and Slew
Alternate Mode
Transistor Driver
Before we leave the topic of GPIO, let’s review a simple transistor driver that can be used in situations where a complete IC solution might be overkill. The GPIO pins of the Raspberry Pi are limited in their ability to drive current. Even configured for full drive, they are limited to 16 mA.
The input signal arrives from the GPIO output on the left side of the circuit and flows through resistor R1, through the base emitter junction to ground. R1 limits this current to a safe value. Resistor R2 connects between the collector of Q1 and the power supply, which can be somewhat higher than +3.3 V. This is safe because the collector base junction is reversed biased. Be careful to not exceed the collector base voltage, however.
This calculated current exceeds the datasheet limit for IC=600 mA, so we now switch to using 600 mA instead. Let’s assume that we only need 100 mA, rather than the absolute limit.
The nearest 10% resistor value is 1.2 kohms.
Inductive Loads
The relay coil needs a reversed biased diode (D1) across it to bleed any reverse kick that occurs when the current is removed from the coil (pins 5 and 2 in the figure). This will have the effect of slowing the release of the contacts. But this is preferred over the inductive spike causing a system crash.
Summary
The source code provided in gp.c is written entirely in the C language and kept to the bare essentials. It not only demonstrates the direct register access steps involved but provides you with code that you can reuse in your own C/C++ programs.
The chapter finished with a brief look at using a driver transistor when a driver is needed. Often an IC is sought when a cheaper one-transistor solution may be enough.