Raspberry Pi IoT In C - gpiod |
Written by Harry Fairhead | |||||
Monday, 30 December 2024 | |||||
Page 3 of 4
GPIO OutputThe next step up 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 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 line set is: GPIO_GET_LINEHANDLE_IOCTL The appropriate struct is: struct gpiohandle_request { __u32 lineoffsets[GPIOHANDLES_MAX]; __u32 flags; __u8 default_values[GPIOHANDLES_MAX]; char consumer_label[32]; __u32 lines; int fd; }; This has to be initialized to sensible values before the ioctl call. The lineoffsets array is simply a list of GPIO line numbers that you want to set. The flags field is one of: GPIOHANDLE_REQUEST_OUTPUT GPIOHANDLE_REQUEST_INPUT GPIOHANDLE_REQUEST_ACTIVE_LOW GPIOHANDLE_REQUEST_OPEN_DRAIN GPIOHANDLE_REQUEST_OPEN_SOURCE 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, which can be retrieved using a GPIO_GET_LINEINFO_IOCTL request and the struct: struct gpioline_info { __u32 line_offset; __u32 flags; char name[32]; char consumer[32]; }; 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: struct gpiohandle_request req; req.lineoffsets[0] = 4; req.lineoffsets[1] = 17; req.flags = GPIOHANDLE_REQUEST_OUTPUT; req.default_values[0] = 0; req.default_values[1] = 0; strcpy(req.consumer_label, "Output test"); req.lines = 2; You can see that we are using GPIO 4 and GPIO 17 and setting both to output. They are also both set to start at zero and are labeled Output test. To actually set up these lines and get a file descriptor to them we need to call ioctl(): fd = open("/dev/gpiochip0", O_RDONLY); ret = ioctl(fd, GPIO_GET_LINEHANDLE_IOCTL, &req); close(fd); 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. Now we have the lines set up as outputs, we can use the request: GPIOHANDLE_SET_LINE_VALUES_IOCTL to set the line states. It should come as no surprise that there is a GET request and both use the struct: struct gpiohandle_data { __u8 values[GPIOHANDLES_MAX]; }; 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: struct gpiohandle_data data; data.values[0] = 0; data.values[1] = 1; while (1) { data.values[0] = !data.values[0]; data.values[1] = !data.values[1]; ret = ioctl(req.fd, GPIOHANDLE_SET_LINE_VALUES_IOCTL, &data); } Again, error handling has been ignored for the sake of simplicity. Putting all this together gives: #include <string.h> #include <stdio.h> #include <errno.h> #include <unistd.h> #include <fcntl.h> #include <sys/ioctl.h> #include <linux/gpio.h> int main(int argc, char **argv) { int fd, ret; struct gpiohandle_request req; req.lineoffsets[0] = 4; req.lineoffsets[1] = 17; req.flags = GPIOHANDLE_REQUEST_OUTPUT; req.default_values[0] = 0; req.default_values[1] = 0; strcpy(req.consumer_label, "Output test"); req.lines = 2; fd = open("/dev/gpiochip0", O_RDONLY); ret = ioctl(fd, GPIO_GET_LINEHANDLE_IOCTL, &req); close(fd); gpiohandle_data data; data.values[0] = 0; data.values[1] = 1; while (1) { data.values[0] = !data.values[0]; data.values[1] = !data.values[1]; ret = ioctl(req.fd, Remember to change gpiochip0 to gpiochip4 if you are running on a Pi 5. If you try this out on a Pi Zero/W you will find that the pulses are faster than you might expect – pulse width 1.5µs giving a frequency of 325kHz. Notice that the switching of the two lines is not in sync. There is a 135ns lag between switching each line. A Pi Zero 2W is a little faster at 1.3µs or 385kHz. On a Pi 4 the pulse is 0.64µs and 766kHz with a lag of 75ns. On a Pi 5 the pulse is 0.92µs and 540kHz These figures should be compared to the performance of the obsolete sysfs approach of 130kHz and the pulse width of 3.6µs on a Pi Zero/W and 450kHz and 1.1µs on a Pi 4, i.e. the new system is roughly twice as fast as sysfs. However, compared to using the BCM2835 library when the Pi Zero/W pulses at about 70 nanoseconds and Pi 4 pulses around 75 nanoseconds, it is more than ten times slower. |
|||||
Last Updated ( Monday, 30 December 2024 ) |