Popular Content

Showing most liked content on 05/14/2017 in all areas

  1. 1 like
    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
  2. 1 like
    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..
  3. 1 like
    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