Originally posted by me on May 15, 2017
In addition to the “traditional Linux” way of working through the file system when using hardware. One can use mmap() and /dev/mem/, when absolute performance is needed, or perhaps if one needs to minimize the load put on the processor when dealing with GPIO hardware. One does need to be aware that when doing so, all operations on the hardware used is done without the kernel knowing. This is kind of a misnomer, as Linux will “eventually” know something is happening, and adjust the sysfs entries accordingly. *Assuming*, Linux even knows such hardware even exists by way of loading the appropriate drivers. Which does not have to be the case. One can essentially write their own “driver” from userspace using this technique. But you may have to set the hardware up manually through it’s respective registers. I’ll leave further explanation up as an exercise to the reader to figure out. As I do not fully understand the implications, for each hardware module on the AM335x processor myself. GPIO, is easy, but something such as PWM, ADC, or I2C, etc requires more than a simple configuration to get working properly.
Anyway, some of this code will probably need further explanation, so I will pick apart some of the code and explain what is happening after the fact.
Code:
#include <stdio.h>
#include <stdlib.h>
#include <stdint.h>
#include <unistd.h>
#include <sys/mman.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <errno.h>
#include <fcntl.h>
#define GPIO0 (0x44E07000)
#define GPIO1 (0x4804C000)
#define GPIO2 (0x481AC000)
#define GPIO3 (0x481AE000)
#define GPIO_SIZE (0x2000)
#define GPIO_DATAOUT (0x13C)
#define GPIO_DATAIN (0x138)
#define PWM1 (1<<2) /*gpio_0*/
#define PWM2 (1<<3) /*gpio_0*/
#define PWM3 (1<<18) /*gpio_1*/
#define PWM4 (1<<22) /*gpio_0*/
#define PWM5 (1<<19) /*gpio_1*/
#define PWM6 (1<<23) /*gpio_0*/
#define Z1IN (1<<12) /*gpio_1*/
#define Z2IN (1<<13) /*gpio_1*/
#define Z3IN (1<<14) /*gpio_1*/
#define Z4IN (1<<15) /*gpio_1*/
#define Z5IN (1<<16) /*gpio_1*/
#define Z6IN (1<<17) /*gpio_1*/
int main(int argc, char *argv[])
{
void *gpio_addr;
unsigned int *gpio0_out;
unsigned int *gpio1_out, *gpio1_in;
int fd = open("/dev/mem", O_RDWR);
gpio_addr = mmap(0, GPIO_SIZE, PROT_READ | PROT_WRITE, MAP_SHARED, fd, GPIO0);
gpio0_out = gpio_addr + GPIO_DATAOUT;
gpio_addr = mmap(0, GPIO_SIZE, PROT_READ | PROT_WRITE, MAP_SHARED, fd, GPIO1);
gpio1_out = gpio_addr + GPIO_DATAOUT;
gpio1_in = gpio_addr + GPIO_DATAIN;
close(fd);
*gpio0_out |= PWM1 + PWM2 + PWM4 + PWM6;
*gpio1_out |= PWM3 + PWM5;
usleep(1000);
printf("PWM1 %u Z1IN %u\n", !!(*gpio0_out & PWM1), !!(*gpio1_in & Z1IN));
printf("PWM2 %u Z2IN %u\n", !!(*gpio0_out & PWM2), !!(*gpio1_in & Z2IN));
printf("PWM3 %u Z3IN %u\n", !!(*gpio1_out & PWM3), !!(*gpio1_in & Z3IN));
printf("PWM4 %u Z4IN %u\n", !!(*gpio0_out & PWM4), !!(*gpio1_in & Z4IN));
printf("PWM5 %u Z5IN %u\n", !!(*gpio1_out & PWM5), !!(*gpio1_in & Z5IN));
printf("PWM6 %u Z6IN %u\n", !!(*gpio0_out & PWM6), !!(*gpio1_in & Z6IN));
printf("\n");
*gpio0_out &= ~(PWM1 + PWM2 + PWM4 + PWM6);
*gpio1_out &= ~(PWM3 + PWM5);
usleep(1000);
printf("PWM1 %u Z1IN %u\n", !!(*gpio0_out & PWM1), !!(*gpio1_in & Z1IN));
printf("PWM2 %u Z2IN %u\n", !!(*gpio0_out & PWM2), !!(*gpio1_in & Z2IN));
printf("PWM3 %u Z3IN %u\n", !!(*gpio1_out & PWM3), !!(*gpio1_in & Z3IN));
printf("PWM4 %u Z4IN %u\n", !!(*gpio0_out & PWM4), !!(*gpio1_in & Z4IN));
printf("PWM5 %u Z5IN %u\n", !!(*gpio1_out & PWM5), !!(*gpio1_in & Z5IN));
printf("PWM6 %u Z6IN %u\n", !!(*gpio0_out & PWM6), !!(*gpio1_in & Z6IN));
return 0;
}
Output:
root@wgd:~/dl-i2c-test# gcc -Wall -o tst tst.c
root@wgd:~/dl-i2c-test# ./tst
PWM1 1 Z1IN 0
PWM2 1 Z2IN 0
PWM3 1 Z3IN 0
PWM4 1 Z4IN 0
PWM5 1 Z5IN 0
PWM6 1 Z6IN 0
PWM1 0 Z1IN 1
PWM2 0 Z2IN 1
PWM3 0 Z3IN 1
PWM4 0 Z4IN 1
PWM5 0 Z5IN 1
PWM6 0 Z6IN 1
So the first thing to understand about this code is that it will return the output immediately after the command is issued. Partly this has to do with using sleep() in the first example, and usleep() in this example. 1 Second sleep, versus a 1000 uSec( 1 millisecond ) sleep. I will say however, that the timing cut off for reading from a GPI with the current kernel I’m using ( which is RT PREEMPT by the way ) is between 500 uSec, and 1000 uSec. Exactly where, I’m not sure, but I’ve tested all the way down to 10 uSec, and the slew rate of the GPI’s at this speed are far too slow. e.g. I get back incorrect readings, even though visual inspection( LEDs ) shows everything working correctly. Now it may be possible to speed up the sysfs method by using usleep() instead, but be aware there would be ~5 syscalls per pin group, and these calls would hinder performance greatly when compared to this method.
Now a bit of code explanation:
#define GPIO0 (0x44E07000)
#define GPIO1 (0x4804C000)
#define GPIO2 (0x481AC000)
#define GPIO3 (0x481AE000)
The base address for each GPIO bank. So how do “We” know these addresses ? Well, you can read the TRM for the AM355x processor, and get this information. But there is an easier way.
root@wgd:~/dl-i2c-test# ls /sys/devices/platform/ocp/*.gpio/gpio/
/sys/devices/platform/ocp/44e07000.gpio/gpio/:
gpio2 gpio22 gpio23 gpio26 gpio3 gpiochip0
/sys/devices/platform/ocp/4804c000.gpio/gpio/:
gpio44 gpio45 gpio46 gpio47 gpio48 gpio49 gpio50 gpio51 gpio60 gpiochip32
/sys/devices/platform/ocp/481ac000.gpio/gpio/:
gpio86 gpio87 gpio88 gpiochip64
/sys/devices/platform/ocp/481ae000.gpio/gpio/:
gpio110 gpio111 gpio112 gpio115 gpio117 gpiochip96
Now, not only is this easier to figure out the base addresses for each GPIO bank. But if like me you do not memorize each gpioxx number, this will tell you which GPIO bank each pin is on. When using a device tree overlay file to configure your pins. Very handy. As glancing at this is much easier than parsing a spread sheet when you’re doing this:
#define PWM1 (1<<2) /*gpio_0*/
#define PWM2 (1<<3) /*gpio_0*/
#define PWM3 (1<<18) /*gpio_1*/
#define PWM4 (1<<22) /*gpio_0*/
#define PWM5 (1<<19) /*gpio_1*/
#define PWM6 (1<<23) /*gpio_0*/
#define Z1IN (1<<12) /*gpio_1*/
#define Z2IN (1<<13) /*gpio_1*/
#define Z3IN (1<<14) /*gpio_1*/
#define Z4IN (1<<15) /*gpio_1*/
#define Z5IN (1<<16) /*gpio_1*/
#define Z6IN (1<<17) /*gpio_1*/
These numbers:
#define GPIO_SIZE (0x2000)
#define GPIO_DATAOUT (0x13C)
#define GPIO_DATAIN (0x138)
You will have to read the TRM, to figure out, if you need to be absolutely certain. However know that the page_file size in Linux will always be a multiple of 4k, and I just so happen to know off the top of my head that each GPIO bank “width” is 8192 bytes. To store all the GPIO configuration registers. Or PAGE_FILE_SIZE * 2.
Now onto something that many people may not know.
printf("PWM1 %u Z1IN %u\n", !!(*gpio0_out & PWM1), !!(*gpio1_in & Z1IN));
!! in this case is not one, but actually two operators. Basically NOT NOT, which essentially works as a shorthand for a ternary conditional check. Returning a value of 0, or 1. Kind of a hack, that is definitely not self explanatory at first glance. Until you work it out in your head what is actually going on. Then, it’s something that becomes obvious. I’d argue that this short hand form is actually as readable as a “proper” ternary conditional check. Which is to say, it takes getting used to, at first.