yyrkoon

Random beaglebone code snippets.

7 posts in this topic

So, this is partly for me, and partly for others who need a refresher, or just do not know how. But I will be making several post here over time on how to write very simply code, to do one thing, or another. These, used in conjunction with a shell script could be very useful / flexible. After several long talks with many people, including some here on these very forums. I've decided that using C, to communicate with hardware, or hardware interfaces is best as can be for many situations. However, when you need to run several tools all at once, and have output formatted in some fashion, or easily modified. Shell scripts are very good at that sort of thing.

Read from a real-time clock

This post I will make about reading from a real-time clock. I spent hours messing around code related to I2C communications, and could never get exactly what I wanted. Plus, I wanted something that output date / time that looked very similar to the date Linux command. This could definitely been done using a shell script, but code size would probably be a lot larger. Additionally, a shell script would very likely be a lot slower, as with a script, one would have to be calling external cmdline tools to perform various operations. This example code is very fast, and prints to screen immediately after issuing the command. Since this command is very simple, and only prints the formatted date / time to screen. This could very easily be called from a shell script, and formatted further if need be.

The real-time clock I'm using for this demonstration is a Maxim DS3232 real-time clock which is very accurate, and also very expensive compared to other real-time clocks. At $7 + US each, it's not cheap. I also had to write my own device tree overlay for this RTC, which strictly speaking is not necessary. One can set the device up from the command line manually as demonstrated for many different RTC's on the web. In fact, all the device tree overlay that I wrote does, is set all this automatically up at boot. As far as teh actual overlay it's self. All I did was modify an existing overlay from the "official" bb-overlays repo on github. https://github.com/beagleboard/bb.org-overlays/blob/master/src/arm/BB-RTC-01-00A0.dts

To look something like this:
 

/*
 * Copyright (C) 2015 Robert Nelson <robertcnelson@gmail.com>
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License version 2 as
 * published by the Free Software Foundation.
 */
/dts-v1/;
/plugin/;

#include <dt-bindings/board/am335x-bbw-bbb-base.h>
#include <dt-bindings/gpio/gpio.h>
#include <dt-bindings/pinctrl/am33xx.h>

/ {
    compatible = "ti,beaglebone", "ti,beaglebone-black", "ti,beaglebone-green";

    /* identification */
    part-number = "BB-RTC-01";
    version = "00A0";

    fragment@2 {
        target = <&i2c2>;
        __overlay__ {

            status = "okay";

            /* shut up DTC warnings */
            #address-cells = <1>;
            #size-cells = <0>;

            /* MCP79410 RTC module */
            rtc@68 {
                compatible = "maxim,ds3232";
                reg = <0x68>;
            };
        };
    };
};

On our cape, the RTC is on bus I2C-2, which is already enabled by default for capemgr. The rest of the above just means that status is okay(load the device ), the kernel module to load is called "ds3232", and the device address on the bus is 0x68.

Now on to the actual C code for reading from /dev/rtc1:
 

#include <stdio.h>
#include <stdlib.h>
#include <linux/rtc.h>
#include <sys/ioctl.h>
#include <sys/time.h>
#include <sys/types.h>
#include <fcntl.h>
#include <unistd.h>
#include <errno.h>

void display_date_time(void)
{
        struct rtc_time rtc_tm;

        int fd = open("/dev/rtc1", O_RDONLY);

        if(fd ==  -1){
                perror("/dev/rtc");
                exit(errno);
        }
        
        /* Read the RTC time/date */
        int retval = ioctl(fd, RTC_RD_TIME, &rtc_tm);
        if (retval == -1) {
                perror("ioctl");
                exit(errno);
        }

        int d = rtc_tm.tm_mday;
        int m = rtc_tm.tm_mon + 1;
        int y = rtc_tm.tm_year + 1900;

        const char *wdays[] = {"Sun","Mon","Tues","Wed","Thur","Fri","Sat"};
        const char *mnths[] = {"Jan","Feb","Mar","Apr","May","June","July","Aug","Sept","Oct","Nov","Dec"};
        
        int wday = (d += m < 3 ? y-- : y - 2, 23*m/9 + d + 4 + y/4- y/100 + y/400)%7;

        fprintf(stdout, "%s %s %02d %02d:%02d:%02d %d UTC\n",
                wdays[wday], mnths[rtc_tm.tm_mon], rtc_tm.tm_mday,
                rtc_tm.tm_hour, rtc_tm.tm_min, rtc_tm.tm_sec, y);
}

int main( int argc, char **argv )
{       
        display_date_time();
            
        return 0;
}

As one can see, most of this code is for formatting the output in a specific way. In this case, the output will look exactly like the output one might expect to see after issuing the command "date". However, this output is fixed to output the date / time in the UTC time zone. As for one of the projects I'm using this in is for devices spread out all over the US, in 3 different time zones, and we do not care so much what the local time zone of that system so much, as much as knowing a given time "standard". e.g. if something fails, and we need to tell a customer what failed, and what time it failed, we can, Then if we need to convert that time to their time zone, easy.

Notice that the read() is handled by ioctl(). . .

 

Output:

root@wgd:~/# gcc -Wall -o read_rtc read_rtc.c
root@wgd:~/# ./read_rtc
Tues May 09 23:08:49 2017 UTC

 

zeke likes this

Share this post


Link to post
Share on other sites

Read from a DS18B20 temperature sensor

Again, very simple code to read from a device, and put that read information out to stdout. In this case, reading from a 1-wire DS18B20 sensor. The pin used is unimportant, so long as that pin is configurable as gpio, and is not already in use by another device. 1-wire is one of the simpler sensors to connect to a beaglebone, and can be plugged directly into one of the two headers on the beaglebone using jumper wires. You need power(3v3), ground, and a gpio pin connected. See the DS18B20 datasheet to determine which pin on the sensor is used for what purpose. As for setup in Linux for this sensor. You can search the web for a guide as to how to do this manually from the cmdline, or you can use a device tree overlay. I used this overlay file as a template, then modified the pin information to reflect the pin I needed to use. https://github.com/beagleboard/bb.org-overlays/blob/master/src/arm/BB-W1-P9.12-00A0.dts

 

C code:

#include <stdio.h>
#include <dirent.h>
#include <string.h>
#include <fcntl.h>
#include <stdlib.h>
#include <unistd.h>
#include <errno.h>

int read_DS18B20(void)
{
        DIR *dir;
        struct dirent *dirent;
        char dev[16];
        char devPath[128];
        char buf[80];
        char tmpData[6];
        char path[] = "/sys/bus/w1/devices";
        ssize_t nread;

        dir = opendir(path);
        if(dir == NULL){
                perror("/sys/bus/w1/devices");
                exit(errno);
        }

        while((dirent = readdir(dir))){
                if (dirent->d_type == DT_LNK && strstr(dirent->d_name, "28-") != NULL){
                        strcpy(dev, dirent->d_name);
                }
        }
        (void)closedir(dir);

        sprintf(devPath, "%s/%s/w1_slave", path, dev);
        int fd = open(devPath, O_RDONLY);
        if (fd == -1){
                perror ("/sys/bus/w1/devices/28-*/w1_slave");
                exit(errno);
        }

        long tempC = 0;
        nread = read(fd, buf, 80);
        if(nread > 0){
                strncpy(tmpData, strstr(buf, "t=") + 2, 5);
                tempC = strtol(tmpData, NULL, 10);
        }
        close(fd);

        return tempC;
}

int main (void)
{       
        float temp = read_DS18B20() * (1 / 1000.0);
        printf("Temp: %.3f C  \n", temp);

        return 0;
}

Output:

root@wgd:~/# gcc -Wall -o read_ds18b20 read_ds18b20.c
root@wgd:~/# ./read_ds18b20
Temp: 25.312 C

 

zeke likes this

Share this post


Link to post
Share on other sites

How to read an ADC

So this bit may seem a little odd to some. Hell I know the hardware, and wrote this really quick snippet as a demonstration, and I think it's odd. The short story here. Is that we have a pin multiplexer on AIN6, and this multiplexer selects an external channel based on a bit pattern sent to it through 3 GPIO pins. That code I won't be showing in this post. but I wanted to point out why I have an odd "mvolts" value. Which indicates maximum input voltage before the voltage limiting resistor network. e.g. the on board ADC pins can only handle 1.8v absolute maximum voltage, and I'm pretty sure without all circuitry voltage drop, outside maximum voltage is supposed to be 0-10v, and wulf probably designed the voltage into the ADC it's self to be less than 1.5v . . . So I started with 10v in mind, saw the reading was definitely too high, and then played a guessing game until the voltage I read from a volt meter, matched what I was reading through the ADC. So there is definite resolution loss here . . .

Anyway, the best and easiest way to load drivers for the ADC is to load the stock ADC overlay from /lib/firmware/.

root@wgd:~# ls /lib/firmware/ |grep ADC
BB-ADC-00A0.dtbo


That is the file to load. Which can be loaded through capemgr via the command line manually, or from /boot/uEnv.txt at boot.
 

ADC C code:

#include <unistd.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <stdio.h>
#include <errno.h>
#include <stdlib.h>

const char *ain6 = "/sys/bus/iio/devices/iio:device0/in_voltage6_raw";

int main()
{
    
    int fd;
    int len;
    char adc[5] = {0};
    
    float madc = 4095.0f;
    float mvolts = 7.7f;       
    float bvolts = (mvolts / madc);

    fd = open(ain6, O_RDONLY);
    if(fd == -1){
            perror("ain6");
            exit(1);
    }
    
    len = read(fd, adc, sizeof(adc - 1));
    
    int adc_val = strtol(adc, NULL, 10);
    float voltage = adc_val * bvolts;

    printf("%f \n", voltage);
    close(fd);
    
    return  0;
}

Output:

root@wgd:~# gcc -Wall -o adc adc.c
adc.c: In function 'main':
adc.c:14:6: warning: variable 'len' set but not used [-Wunused-but-set-variable]
  int len;
      ^
root@wgd:~# ./adc
0.030085

A couple things to notice here. First is the warning I'm getting back from the compiler. This is because I'm using the -Wall option, which pretty much tells the compiler to use strict reporting of warnings. It is my belief that one should always at least use the -Wall compiler flag. Then we should treat these warning as if they're errors, and correct them. Which( and yeah I hate explanations like this too ) I'm ignoring for this one situation. Basically "len" is a return value from read() which can let us know how many bytes were read out of the file, and we absolutely should test this value for a non negative number. Then act on negative numbers as an error, which could be done a few different ways. For this demo, I was not sure in a pinch what would be a better way to handle that potential error. Mostly because I know these values will always be 0-4095, unless there is a problem with the hardware. At which point we're done anyhow( probably a blown processor ). One way to deal with this kind of error, would be to use perror() followed by exit(errno) in an if block. But at this point I'm fairly confident the system would not be running anyway . . .However, the values coming out of the ADC module should always be 1-4 characters, so how do we test for no characters ? NULL, but if we're reading out a NULL, how did our code make it thus far anyhow ? Additionally, if we're reading more than 4 character . . .Not only do I believe this to be impossible because of the way I wrote this code, but if it were somehow possible. We'd have a buffer overrun. Which may be a very good idea to test for. If for nothing else, good practice ? You decide.

Secondly, the really low, but not absolute zero value. Well, I've come to realize that when working with circuits of this nature, that can not tied to ground, or pulled high. You're basically "floating" but close to a low, or a high . . . again, I'm not exactly an EE, so maybe someone who cares to can elaborate further. But I always think of this sort of situation as induced parasitic voltages from the circuitry. I'm definitely all ears if someone has a better explanation, or insight into this. .  .But I've tested these readings against a volt meter, and am reasonably happy that I'm "close enough" for my own purposes. Someday, perhaps I'll buy a USB computer style Oscilloscope, in hopes to enlighten myself further..

zeke likes this

Share this post


Link to post
Share on other sites

Working with GPIO:

So where to start. GPIO is probably one of the most easiest things to work with on a beaglebone. Once you understand a few things. First, the kernel through sysfs has no idea what pins are tied to which header pin. So one needs to find a spread sheet that will explain which header pin is attached to which GPIO pin through the sysfs file structure. The base path for the sysfs gpio path is /sys.class/gpio/. However the explanation of how all this works is fairly lengthy, and there are a lot of guides dating back to the original bealgebone(white) as to how all this works. In short, if you need to know this information I suggest you search the web for information. This is the "manual" way of setting up GPIO.

Another alternative is again to use a device tree overlay file. Which in of it's self is initially a lengthy process to understand. In short: https://github.com/beagleboard/bb.org-overlays/blob/master/src/arm/univ-nhdmi-00A0.dts#L232-#L234 The highlighted text here is defining a pin number, and the mode that pin needs to be set into for GPIO operation, with pullup, and RxActive enabled. These files for the purpose of this explanation are not exactly the best to use. Perhaps at some point in the future Ill create a simple overlay file, for the purpose of  better explanation. As it is, this particular overlay is for Universal IO, and it covers most, if not all pins, with most if not all possible modes for each pin. Used with config-pin this allows one to configure each pin as needed dynamically from the command line . . .

So onto some simple code. First, again, some explanation is required. For our board we have some PWM pins, and some input pins, among other things. Both PWM, and input only use 6 pins each. In order to test the circuitry of our boards, To test the our capes circuitry, we instead use the PWM pins as GPO's, the input pins as GPI's, and have a test header connecting each "zone" together from GPO to GPI. Additionally, there is an LED connected to each GPO on this test header for visual inspection.

Code:

#include <unistd.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <stdio.h>
#include <errno.h>
#include <stdlib.h>

#define HIGH    1
#define LOW    0

#define PWM1    2
#define PWM2    3
#define PWM3    50
#define PWM4    22
#define PWM5    51
#define PWM6    23

#define Z1IN    44
#define Z2IN    45
#define Z3IN    46
#define Z4IN    47
#define Z5IN    48
#define Z6IN    49

void set_pin(int pin_num, int value)
{
    char gpio_path[40] = {0};
    char str_value[2] = {0};

    sprintf(str_value, "%d", value);
    sprintf(gpio_path, "/sys/class/gpio/gpio%d/value", pin_num);

    int fd = open(gpio_path, O_WRONLY);
    if(fd == -1){
            perror(gpio_path);
            exit(1);
    }

    int nread = write(fd, str_value, 1);
    if( nread < 0){
        perror("write()");
        exit(1);
    }

    close(fd);
}

int get_pin(int pin_num)
{
    char gpio_path[40] = {0};
    char str_value[2] = {0};

    sprintf(gpio_path, "/sys/class/gpio/gpio%d/value", pin_num);

    int fd = open(gpio_path, O_RDONLY);
    if(fd == -1){
            perror(gpio_path);
            exit(1);
    }

    read(fd, str_value, sizeof(str_value - 1));

    close(fd);

    return strtol(str_value, NULL, 10);
}

int main()
{

    set_pin(PWM1, LOW);
    set_pin(PWM2, LOW);
    set_pin(PWM3, LOW);
    set_pin(PWM4, LOW);
    set_pin(PWM5, LOW);
    set_pin(PWM6, LOW);
    sleep(1);
    printf("PWM1: %i Z1IN: %i \n", get_pin(PWM1), get_pin(Z1IN));
    printf("PWM2: %i Z2IN: %i \n", get_pin(PWM2), get_pin(Z2IN));
    printf("PWM3: %i Z3IN: %i \n", get_pin(PWM3), get_pin(Z3IN));
    printf("PWM4: %i Z4IN: %i \n", get_pin(PWM4), get_pin(Z4IN));
    printf("PWM5: %i Z5IN: %i \n", get_pin(PWM5), get_pin(Z5IN));
    printf("PWM6: %i Z6IN: %i \n", get_pin(PWM6), get_pin(Z6IN));

    printf("\n");

    /***********************************************************/
    set_pin(PWM1, HIGH);
    set_pin(PWM2, HIGH);
    set_pin(PWM3, HIGH);
    set_pin(PWM4, HIGH);
    set_pin(PWM5, HIGH);
    set_pin(PWM6, HIGH);
    sleep(1);
    printf("PWM1: %i Z1IN: %i \n", get_pin(PWM1), get_pin(Z1IN));
    printf("PWM2: %i Z2IN: %i \n", get_pin(PWM2), get_pin(Z2IN));
    printf("PWM3: %i Z3IN: %i \n", get_pin(PWM3), get_pin(Z3IN));
    printf("PWM4: %i Z4IN: %i \n", get_pin(PWM4), get_pin(Z4IN));
    printf("PWM5: %i Z5IN: %i \n", get_pin(PWM5), get_pin(Z5IN));
    printf("PWM6: %i Z6IN: %i \n", get_pin(PWM6), get_pin(Z6IN));

    return  0;
}

Output:

root@wgd:~/dl-i2c-test# gcc -Wall -o read_zonein read_zonein.c
root@wgd:~/dl-i2c-test# ./read_zonein
PWM1: 0 Z1IN: 1
PWM2: 0 Z2IN: 1
PWM3: 0 Z3IN: 1
PWM4: 0 Z4IN: 1
PWM5: 0 Z5IN: 1
PWM6: 0 Z6IN: 1

PWM1: 1 Z1IN: 0
PWM2: 1 Z2IN: 0
PWM3: 1 Z3IN: 0
PWM4: 1 Z4IN: 0
PWM5: 1 Z5IN: 0
PWM6: 1 Z6IN: 0

As one can see the logic between inputs, and output is reversed. This is of course intentional.

Share this post


Link to post
Share on other sites

Working with GPIO, closer to the hardware:

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 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 glace.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.

Share this post


Link to post
Share on other sites

Simple Line / mux select

So, this is a variation on using GPIO, via direct memory access. The benefit to using this code, or similar. Is that there is only two syscalls for each line / mux select. Versus 12 syscalls if using the standard "Linux way" using open(), write(), read(), and close(). That's PER pin. yikes. Keep in mind, I haven't put a ton of thought into this code, so it could very well be rough around the edges, but it does work.

So for our particular application. We have a Line select for AIN6 on the beaglebone. As we needed more ADC channels. Again, for our own application, we needed 5 additional channels, with valid cmd line tool only accepting values 0-3, and 7. strtol() is set to base 16, so as not to interpret ASCII characters as the numerical value zero. Or potentially causing a seg fault. Put more correctly, if using base "10" as the numerical base value, values like 0x7, or absurdly large strings of characters could be interpreted as 0, and potentially something else undesirable.
 

#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 GPIO2           (0x481AC000)
#define GPIO_SIZE       (0x2000)

#define GPIO_DATAOUT    (0x13C)
#define GPIO_DATAIN     (0x138)

#define MA0 (1<<26)     /*gpio_0*/
#define MA1 (1<<23)     /*gpio_2*/
#define MA2 (1<<24)     /*gpio_2*/

void line_select(uint8_t asp_value)
{
        void *gpio_addr;
        unsigned int *bank0_out, *bank2_out;

        int fd = open("/dev/mem", O_RDWR);

        gpio_addr = mmap(0, GPIO_SIZE, PROT_READ | PROT_WRITE, MAP_SHARED, fd, GPIO0);
        bank0_out = gpio_addr + GPIO_DATAOUT;
        gpio_addr = mmap(0, GPIO_SIZE, PROT_READ | PROT_WRITE, MAP_SHARED, fd, GPIO2);
        bank2_out = gpio_addr + GPIO_DATAOUT;

        close(fd);
        
        (asp_value & (1 << 0)) ? (*bank0_out |= MA0) : (*bank0_out &= ~(MA0));
        (asp_value & (1 << 1)) ? (*bank2_out |= MA1) : (*bank2_out &= ~(MA1));
        (asp_value & (1 << 2)) ? (*bank2_out |= MA2) : (*bank2_out &= ~(MA2));

        printf("Line select: %u%u%u\n",
                !!(*bank2_out & MA2), !!(*bank2_out & MA1), !!(*bank0_out & MA0));
}

int main(int argc, char *argv[])
{        
        if(argc != 2) exit(1);

        int argv2 = strtol(argv[1], NULL, 16);
        if(argv2 != 7){
                if(argv2 < 0 || argv2 > 3) exit(1);
        }

        line_select(argv2);

        return 0;
}

 

Output:

root@wgd:~/dl-i2c-test# nano tst.c
root@wgd:~/dl-i2c-test# gcc -Wall -o tst tst.c
root@wgd:~/dl-i2c-test# ./tst 0
Line select: 000
root@wgd:~/dl-i2c-test# ./tst 1
Line select: 001
root@wgd:~/dl-i2c-test# ./tst 2
Line select: 010
root@wgd:~/dl-i2c-test# ./tst 3
Line select: 011
root@wgd:~/dl-i2c-test# ./tst 4
root@wgd:~/dl-i2c-test# ./tst 0x7
Line select: 111
root@wgd:~/dl-i2c-test# ./tst 0xFF
root@wgd:~/dl-i2c-test# ./tst -1
root@wgd:~/dl-i2c-test#

 

Share this post


Link to post
Share on other sites

Create an account or sign in to comment

You need to be a member in order to leave a comment

Create an account

Sign up for a new account in our community. It's easy!


Register a new account

Sign in

Already have an account? Sign in here.


Sign In Now