/* compile with `gcc -std=gnu99 -Wall -o assign2 main.c gpio.c -lpthread` *//* compile with `gcc -std=gnu99 -Wall -o assign2 main.c gpio.c -lpthread` */Goal: make a program that asks the user repeatedly for a frequency, then blink the two LEDs on the expansion board with this frequency.
LED D1 is on GPIO87, LED D2 is on GPIO89. They both have a direction, which is either in or out, and a value, which is either 0 or 1.
The GPIOs are mapped into the file system by the Linux sysfs subsystem, which gives us convenient files to set the direction and value of each GPIO pin. This happens in gpio.c.
To ask the user for a frequency and do the blinking at the same time,
we will use the pthread library. The main thread will do the input
and ouput for the user, the thread will do the blinking.
/* */Usual includes plus signals, threads, and time functions.
#include <stdio.h>
#include <stdint.h>
#include <unistd.h>
#include <signal.h>
#include <pthread.h>
#include <stdlib.h>
#include <time.h>We again have a volatile atomic flag for signaling the end of the program.
static means this variable is not local to every thread but initialized
once before main().
static volatile sig_atomic_t done = 0;
void sigint(int signo) {
done = 1;
}In addition we have another volatile variable to communicate the chosen blinking frequency between threads.
static volatile double frequency = 0;This will be the blinking thread’s main function.
void *blink_at_frequency(void *);The assigment stipulated we write some specific functions to handle the interaction with the GPIOs. I put them in a separate file gpio.c.
The GPIO code uses these types to make the arguments and return values
of its functions more readable. This should be in its own .h file,
and from the next assignment on it is.
enum GPIO {
GPIO87,
GPIO89
};
enum Value {
OFF,
ON
};
enum Direction {
OUT,
IN
};
int gpio_init();
enum Direction gpio_get_dir(enum GPIO gpio);
void gpio_set_dir(enum GPIO gpio, enum Direction dir);
enum Value gpio_get_value(enum GPIO gpio);
void gpio_set_value(enum GPIO gpio, enum Value value);int main()
{Set up interrupt handler to end gracefully on Ctrl-C. This is exactly the same code as in the previous assignment.
struct sigaction act; {
act.sa_handler = sigint;
act.sa_flags = 0;
sigfillset(&act.sa_mask);
}
sigaction(SIGINT, &act, NULL);Start the blinking thread. As frequency is 0 initially, the
LEDs will just be off.
pthread_t blink;
pthread_create(&blink, NULL, blink_at_frequency, NULL);We repeatedly read in a new frequency from the user.
double new_freq;
while (!done) {
printf("Change frequency (Hz):\n");
int read = scanf(" %lf%*c", &new_freq);remove ^C from STDERR if scanf was interrupted
if (read < 0) {
fprintf(stderr, "\r");
continue;
}The user might have entered whatever instead of a valid number, so we get 0 variables filled, and we need to read over whatever garbage is left in STDIN.
if (read == 0) {
scanf("%*[^\n]%*c");
continue;
}Ignore negative values.
if (new_freq >= 0) {
frequency = new_freq;
}
}When we are done, print a nice exit message and wait for the blinking thread to end.
printf("Exiting program...\n");
pthread_join(blink, NULL);
return 0;
}The thread main function takes and returns a pointer to anything, but we don’t use that here.
void *blink_at_frequency(void * _)
{If gpio_init returns something else than 0, we know something went
wrong and we signal to the main thread to exit.
See gpio_init.
if (gpio_init()) {
done = 1;
return NULL;
}Save the current directions so we can be a good citizen and restore them a the end of the program.
See gpio_get_dir, gpio_set_dir, and gpio_get_value.
enum Direction gpio87_dir = gpio_get_dir(GPIO87);
enum Direction gpio89_dir = gpio_get_dir(GPIO89);
gpio_set_dir(GPIO87, OUT);
gpio_set_dir(GPIO89, OUT);
gpio_set_value(GPIO87, OFF);
gpio_set_value(GPIO89, OFF);We create the blinking effect by sleeping for a specific time given in nanoseconds.
struct timespec sleeptime;
double duty;
while (!done) {At the beginning, we only check every 0.5 seconds whether the user has given us a new frequency.
if (frequency == 0) {
sleeptime.tv_sec = 0;
sleeptime.tv_nsec = 500000000l;
nanosleep(&sleeptime, NULL);
continue;
}Normal operation: calculate the duty cycle in nanoseconds, then toggle the LEDs after this amount of time.
duty = (1.0 / frequency) * 1e9;
sleeptime.tv_sec = (uint32_t)(duty / 1000000000l);
sleeptime.tv_nsec = ((uint64_t)duty) % 1000000000l;Switch LED GPIO values. We don’t need to read the value from the GPIO file if we are the only program running on the chip, but it’s good form to be suspicious of external changes.
See gpio_get_value and gpio_set_value.
enum Value d1 = gpio_get_value(GPIO87);
enum Value d1_next = d1 == OFF ? ON : OFF;
enum Value d2_next = d1 == OFF ? OFF : ON;
gpio_set_value(GPIO87, d1_next);
gpio_set_value(GPIO89, d2_next);
nanosleep(&sleeptime, NULL);
}Shut off the LEDs and restore the GPIO directions to leave the system in the same state as before.
gpio_set_value(GPIO87, OFF);
gpio_set_value(GPIO89, OFF);
gpio_set_dir(GPIO87, gpio87_dir);
gpio_set_dir(GPIO89, gpio89_dir);
return NULL;
}