/* compile with `gcc -std=gnu99 -Wall -o assign3 main.c gpio.c -lpthread` *//* compile with `gcc -std=gnu99 -Wall -o assign3 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. The blinking must alternate between both LEDs.
This is in essence the same as assignment 2, just with a SIGALRM timer to make the blinking work.
The timing for the repeated setting of the GPIO values should be done with a timer and a SIGALARM signal handler.
/* */This is necessary so the macro TIMESPEC_TO_TIMEVAL is available.
#define _GNU_SOURCEIncludes are the same as in the previous assignment, signals, threads,
time functions. Additionally we include semaphore.h to have something
to wait on for the timer to fire (explained in detail later).
#include <stdio.h>
#include <fcntl.h>
#include <stdint.h>
#include <unistd.h>
#include <signal.h>
#include <pthread.h>
#include <stdlib.h>
#include <time.h>
#include <sys/time.h>
#include <semaphore.h>#include "gpio.h"Same as before, see assignment 2.
static volatile sig_atomic_t done = 0;
void sigint(int signo) {
done = 1;
}
static volatile double frequency = 0;
void *blink_at_frequency(void *);This is almost unchanged from before, see assignment 2.
int main()
{
struct sigaction act; {
act.sa_handler = sigint;
act.sa_flags = 0;
sigfillset(&act.sa_mask);
}
sigaction(SIGINT, &act, NULL);
pthread_t blink;
pthread_create(&blink, NULL, blink_at_frequency, NULL);
double new_freq;The only difference is that scanf can be interrupted by both SIGINT
and SIGALRM, so we have to differentiate between the two. On SIGALRM
we just rerun the loop, but don’t print the frequency prompt again.
int prompt = 1;
while (!done) {
if (prompt) {
printf("Change frequency (Hz):\n");
} else {
prompt = 1;
}
int read = scanf(" %lf%*c", &new_freq);
if (read < 0) {
if (done) {
fprintf(stderr, "\r");
}
prompt = 0;
continue;
}
if (read == 0) {
scanf("%*[^\n]%*c");
continue;
}
if (new_freq >= 0) {
frequency = new_freq;
}
}
printf("Exiting program...\n");
pthread_join(blink, NULL);
return 0;
}In CS3 semaphores were mentioned in passing when talking about threads.
A sempahore is a value that is used to synchronize between multiple
concurrent threads of execution. The idea is the same as the done
flag we use for signaling the end of the program. Multiple threads can
observe the semaphore and act when it changes.
The clue here is that for sem_t there is a function sem_wait, which
blocks the thread until sem_post is called on the same semaphore.
We could do this with a busy-loop while (blocked) but that takes up
precious CPU cycles.
static sem_t block;The signal handler for SIGALRM unblocks the semaphore.
void timed_unblock(int signo)
{
sem_post(&block);
}This is mostly the same as in assigment 2, but instead of using nanosleep
we set up a SIGALRM handler and pass the wait time to setitimer.
void *blink_at_frequency(void * _)
{
if (gpio_init()) {
done = 1;
return NULL;
}
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);Setup the SIGALRM handler if we can initialize the semaphore.
int sem_good = 1;
struct sigaction act; {
act.sa_handler = timed_unblock;
act.sa_flags = 0;
sigfillset(&act.sa_mask);
}
if (sem_init(&block, 0, 0) < 0) {
sem_good = 0;
done = 1;
perror("Could not initialize semaphore");
} else {
sigaction(SIGALRM, &act, NULL);
}setitimer wants a struct that contains a timeval,
struct timespec sleeptime;
struct itimerval timeout = {0};
double duty;
while (!done) {
if (frequency == 0) {
sleeptime.tv_sec = 0;
sleeptime.tv_nsec = 500000000l;
} else {
duty = (1.0 / frequency) * 1e9;
sleeptime.tv_sec = (uint32_t)(duty / 1000000000l);
sleeptime.tv_nsec = ((uint64_t)duty) % 1000000000l;
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);
}so we have to convert the timespec struct from the previous
assigment to a timeval.
TIMESPEC_TO_TIMEVAL(&timeout.it_value, &sleeptime);Set the timer and block on the semaphore.
setitimer(ITIMER_REAL, &timeout, NULL);
sem_wait(&block);
}Clean up when we’re done.
if (sem_good) {
sem_destroy(&block);
}
gpio_set_value(GPIO87, OFF);
gpio_set_value(GPIO89, OFF);
gpio_set_dir(GPIO87, gpio87_dir);
gpio_set_dir(GPIO89, gpio89_dir);
return NULL;
}