/* compile with `gcc -std=gnu99 -Wall -o assign4 main.c gpio.c blink.c -lpthread` *//* compile with `gcc -std=gnu99 -Wall -o assign4 main.c gpio.c blink.c -lpthread` */Goal: make a program that repeatedly reads the value of the potentiometer, outputs the corresponding voltage (0-1.8 V), and converts the value into a frequency to blink the two LEDs on the expansion board.
The potentiometer on the expansion board is wired up to AIN0, which is
an analog input of the ADC on the PocketBeagle. The ADC is mapped in the
ADC_TSC subsystem registers starting at 0x44E0_D000.
Looking in /sys/devices/platform/ocp we can find the device 44e0d000.tscadc
which maps to adc.0.auto, which exports its lines in the iio:device0
device. We can find this device also in /sys/bus/iio/devices/.
AIN0 therefore can be accessed over
/sys/bus/iio/devices/iio:device0/in_voltage0_raw.
According to the AM3358 manual, the ADC has a resolution of 12 bits, which
means 4096 different values. One could also experiment with both extremes
of the potentiometer to reveal that the voltage level is mapped to a range
from 0 to 4095 inclusive.
For the blinking, we re-use most of the the code from assignment 3.
As the blinking of the LEDs cannot really be distinguished beyond 70 Hz or so, we map the input onto the range 1 to 33 Hz and use that as the frequency.
#include <stdio.h>
#include <signal.h>
#include <pthread.h>
#include <stdlib.h>
#include <time.h>I split up the blinking into its own file blink.c, the GPIO code extended by the AIN0 read function can be found in gpio.c.
#include "gpio.h"
#include "done.h"
#include "blink.h"15 ms in nanoseconds ~ 66 Hz, this is for polling the analog input.
#define SAMPLE_RATE 15000000lWe declared these global communication variables in done.h and blink.h,
now we have to define them.
volatile double frequency = 0;
volatile sig_atomic_t done = 0;SIGINT sigaction handler
void sigint(int signo) {
done = 1;
}This is again very similar to the last assignment.
int main()
{
if (gpio_init()) {
return 1;
}Setup a SIGINT handler.
struct sigaction act; {
act.sa_handler = sigint;
act.sa_flags = 0;
sigfillset(&act.sa_mask);
}
sigaction(SIGINT, &act, NULL);Start the blinker thread.
pthread_t blink;
pthread_create(&blink, NULL, blink_at_frequency, NULL);
double new_freq, voltage;
int ain = -2;
printf("Please change the potentiometer.\n");We do not want to check AIN0 in a busy loop, so we sleep for a little.
struct timespec sleep; {
sleep.tv_sec = 0;
sleep.tv_nsec = SAMPLE_RATE;
}Repeatedly read the analog input.
while (!done) {
int new_ain = ain_get_value();Reading AIN0 was jittery on my BeagleBone Black, so I added the option to only consider larger changes in value.
#ifdef AVOID_JITTER
if (abs(new_ain - ain) > 1) {
#endif
ain = new_ain;Map frequency 0-4095 to 1-33 Hz (4096/128 = 32).
new_freq = 1.0 + (ain / 128.0);Map voltage to 0-1.8.
voltage = 1.8 * (ain / 4095.0);
if (new_freq != frequency) {
frequency = new_freq;
printf("Voltage is %.4lf V\t\tFrequency is %.2lf Hz\n", voltage, frequency);
}
#ifdef AVOID_JITTER
}
#endif
nanosleep(&sleep, NULL);
}When interrupted by SIGINT, print a nice exit message.
fprintf(stderr, "\r");
printf("Exiting program...\n");If the blinking frequency is really low, join might wait a long time, so unblock the blinker before.
blink_unblock(SIGALRM);
pthread_join(blink, NULL);
return 0;
}