Pi IoT In Python Using Linux Drivers - GPIO Using Ioct
Written by Mike James & Harry Fairhead   
Monday, 29 July 2024
Article Index
Pi IoT In Python Using Linux Drivers - GPIO Using Ioct
The struct module
GPIO Output
The complete program

GPIO Output

The next step is to set a GPIO line to output and use it. This involves only a few more ideas than the previous example. We need to go through the same steps, but in this case we first use the GPIO chip to make a GPIO handler device. This represents multiple GPIO lines, i.e. you can work with a group of lines, not just one line at a time. This is a two-stage process. First get the GPIO chip and then ask it to configure the GPIO lines and return a new file descriptor to the GPIO lines. The request constant for setting a line to a particular state and returning a new file descriptor that references the set line is:

GPIO_GET_LINEHANDLE_IOCTL=0xC16C B403

The appropriate C struct is:

struct gpiohandle_request {
	__u32 lineoffsets[64];
	__u32 flags;
	__u8 default_values[64];
	char consumer_label[32];
	__u32 lines;
	int fd;
};

The corresponding Python Struct object is:

gpiohandle_request = struct.Struct("64L L 64B 32s L L")

This has to be initialized to sensible values before the ioctl call. The lineoffsets is simply a list of GPIO line numbers that you want to set and the flags field is some combination of:

  • GPIOHANDLE_REQUEST_INPUT=0x01
  • GPIOHANDLE_REQUEST_OUTPUT=0x02
  • GPIOHANDLE_REQUEST_ACTIVE_LOW=0x04
  • GPIOHANDLE_REQUEST_OPEN_DRAIN=0x08
  • GPIOHANDLE_REQUEST_OPEN_SOURCE=0x10

All of the lines specified in lineoffsets are set to the same state. Notice that you can OR the flags together where this makes sense, for example to request an active low output with an open drain. Two additional flags will be made available in the future:

  • GPIOHANDLE_REQUEST_PULL_UP
  • GPIOHANDLE_REQUEST_PULL_DOWN

The default_values array sets output lines to high or low, depending on whether you store 1 or 0 in each element.

The consumer_label assigns a label to each of the lines.

Finally the lines field determines the number of lines being used, i.e. the number of elements of the lineoffsets array that contain meaningful data. The final fd field is used to return a file descriptor for the lines.

Now we are ready to write the program. First we set up the gpiohandle_request struct:

lines = [0]*64
lines[0] = 4
lines[1] = 17
values = [0]*64
buffer = gpiohandle_request.pack(*lines, 
GPIOHANDLE_REQUEST_OUTPUT, *values, b"Output test", 2, 0)

You can see that we are using GPIO4 and GPIO17 and setting both to output. They are also both set to start at 0 and are labeled Output test.

To actually set up these lines and get a file descriptor to them we need to call ioctl():

f = io.open("/dev/gpiochip0", "rb", buffering=0)
result = fcntl.ioctl(f, GPIO_GET_LINEHANDLE_IOCTL,
buffer) f.close()

Again, no error checking is included. Notice that we can close the GPIO chip as we now have the file descriptor to the lines that have been set up. Opening the line returns a file descriptor in the gpiohandle_request struct – in this case in the final four bytes of the result.

The simplest way of getting the file descriptor is to use:

fL = struct.unpack_from("L", result, 360)[0]

You don’t export or unexport a line, but you have ownership of the line until you close the file. However, in this case closing the file isn’t a matter of

fL.close()

because f really is a file descriptor and hence a simple integer. The solution is to use the os module’s close function:

os.close(fL)

Notice that you have to close the file if you want to open the line in a different configuration.

 

Now we have the lines set up as outputs, we can use the request:

GPIOHANDLE_SET_LINE_VALUES_IOCTL=0xC040B409

to set the line states.

It should come as no surprise that there is also a corresponding GET request:

GPIOHANDLE_GET_LINE_VALUES_IOCTL =0xC040B408

and both use the C struct:

struct gpiohandle_data {
	__u8 values[GPIOHANDLES_MAX];
};

which has the Python equivalent:

gpiohandle_data = struct.Struct("64B")

When you use SET the values array specifies what the lines should be set to. When you use GET the values returned are the states of the lines.

Now we can write a loop that toggles the GPIO lines we have set up:

values[0] = 1
values[1] = 0
state1 = gpiohandle_data.pack(*values)
values[0] = 0
values[1] = 1
state2 = gpiohandle_data.pack(*values)
while(True):
    result = fcntl.ioctl(f,
GPIOHANDLE_SET_LINE_VALUES_IOCTL, state1) result = fcntl.ioctl(f,
GPIOHANDLE_SET_LINE_VALUES_IOCTL, state2)

Notice that we have to use two byte strings, state1 and state2, because you cannot change byte strings. Again, error handling has been ignored for the sake of simplicity.



Last Updated ( Tuesday, 30 July 2024 )