Göm meny

TSEA81 - Datorteknik och realtidssystem

Notera att dessa sidor enbart finns i engelsk version

TSEA81 - Computer Engineering and Real-time Systems

Assignment 2 : Alarm Clock

Introduction

In this assignment, a real-time program implementing an alarm clock is developed. Semaphores and mutexes are used for task synchronization and for protection of shared data. The alarm is activated using asymmetric synchronization.

The purpose of the assignment is to provide skills and knowledge required for implementation of a small real-time program, using a real-time operating system.

The assignment demonstrates how a small real-time program, which uses a real-time capable operating system, can be designed and implemented.

Requirements to pass

The requirements to pass this assignment are:

  • The developed software, as required in Section Assignment tasks, is demonstrated.
  • The source code of the developed software is described verbally. The source code shall be well structured, and commented.
The above mentioned requirements to pass shall be fulfilled before the deadline for this assignment, as specified in the course schedule.

Preparations

As a preparation for the assignment, it is recommended to study lecture notes 1 - Introduction, 2 - Shared Resources, and 3 - Task synchronization, up to and including the section Assignment 2 - Alarm Clock.

Required software

The assignment uses C and pthreads. It is recommended that you use a Makefile, similar to the one in assignment 1, to compile and link your program.

The assignment will be evaluated during a lesson in the course, using computers with Linux.

The actual assignment tasks is easiest to do in Linux, but it may be possible to do it on MacOS X as well as Windows (if you have Cygwin installed), although we have not tested the later two.

Assignment tasks

A real-time program implementing an alarm clock shall be developed.

Real-time program requirements

The program shall fulfil the following requirements:

  • A clock with alarm functionality shall be implemented.
  • It shall be possible to set the time.
  • It shall be possible to set the alarm time.
  • The alarm shall be enabled when the alarm time is set.
  • The alarm shall be activated using asymmetric synchronization when the alarm is enabled, and when the current time is equal to the alarm time.
  • An activated alarm must be acknowledged.
  • Acknowlegement of an alarm shall lead to the alarm being disabled. The alarm is enabled again when a new alarm time is set.
  • An alarm which is not acknowledged shall be repeated every 1.5 seconds.
  • The thread which is responsible for repeating the alarm very 1.5 seconds must not consume any CPU time when the alarm is not activated.
  • The program shall communicate with a graphical user interface, where the current time shall be displayed, and where the alarm time shall be displayed when the alarm is enabled.
  • It shall be possible to terminate the program, using a command which is sent from the graphical user interface.

Implementation requirements

The implementation of the real-time program shall fulfil the following requirements:

  • The program shall be written in C, using pthreads to implement parallel activities and time handling.
  • Your solution to this assignment should be based on the code present in the set_clock program from assignment 1.
  • The solution shall be modularized, with set_clock.c as the main program using the four modules clock, display, ui and communication (see below), hence the actual clock should be declared as static in the clock module.
  • The program shall communicate with a graphical user interface.
  • There shall be one task implementing the clock, with as good precision as possible using clock_nanosleep (see Time synchronization)
  • There shall be one task handling the alarm.
  • There shall be one task which reads messages from the graphical user interface.
  • Asymmetric synchronization, implemented using a semaphore, shall be used for activation of the alarm.
  • The program shall be well structured and well orqanized.

Communication with a graphical user interface

The communication between the real-time program and the graphical user interface shall be performed according to the following specification:

Messages from the graphical user interface to the real-time program shall be sent as text strings. The text string

set %d %d %d

where %d denotes an integer number, shall be used for setting the time.

The text string

alarm %d %d %d

where %d denotes an integer number, shall be used for setting the alarm time.

The text string

reset

shall be used for acknowledgment of an alarm.

The graphical user interface used in Assignment 1 - Introduction, Shared Resources contains functionality for termination of the real-time program. This is done by sending the text string

exit

from the graphical user interface to the real-time program. This string must therefore be handled by the real-time program.

Messages from the real-time program to the graphical user interface shall be sent using functionality available in a program module display. The program module display consists of two files, display.h and display.c.

The files can be downloaded using the links display.h and display.c, and shall be placed in the same directory as the other source code files of the real-time program. Further, you will need the files si_comm.c, si_comm.h, si_ui.c and si_ui.h from assignment 1. You will also need to update the Makefile from assignment 1 so that the new files are also used when compiling your modified set_clock program. Note that you need to add the display.c file name to both the line containing the dependencies and the compilation command whereas the header file display.h only needs to be added to the line containing the dependencies. (If you are not familiar with Makefiles, you can take a look at a Makefile tutorial for more information, although the Makefiles in this course have been kept fairly simple.)

A short description of the functionality obtained from the program module display can be given by describing the available functions, as follows:

  • display_init is used for initialisation, and shall be called from the main function of the program, before the processes are created.
  • display_time displays the current time, and shall be called by the clock process.
  • display_alarm_time shows the current alarm time, and shall be called when a new alarm time is set.
  • erase_alarm_time erases the displayed alarm time, and shall be called when the user acknowledges an alarm.
  • display_alarm_text is used to show an alarm activation, and shall be called when the user shall be informed that the alarm has been activated, i.e. when the alarm is activated the first time, and when the alarm is activated repeatedly (which is every 1.5 seconds, according to the above stated requirements).
  • erase_alarm_text erases the information displayed by display_alarm_text, and shall be called when the user acknowledges an alarm.

Implementation recommendations

The reserved word struct can be used to create a data structure containing the variables belonging to the clock. It is possible to define a data type for a time, represented using hours, minutes, and seconds, as

/* time data type */ 
typedef struct 
{
    int hours; 
    int minutes; 
    int seconds; 
} time_data_type; 

It is then possible to define a data type for the clock, according to

/* clock data type */ 
typedef struct
{
    /* the current time */ 
    time_data_type time; 
    /* alarm time */ 
    time_data_type alarm_time; 
    /* alarm enabled flag */ 
    int alarm_enabled; 

    /* semaphore for mutual exclusion */ 
    pthread_mutex_t mutex; 

    /* semaphore for alarm activation */ 
    sem_t start_alarm; 

} clock_data_type; 

A variable can be declared as

/* the actual clock */ 
static clock_data_type Clock; 

where the reserved word static has been used. This use of static means that the declared variable is visible only in the file where it is declared.

A function for initialisation of the clock can now be written as

/* clock_init: initialise clock */ 
void clock_init(void)
{
    /* initialise time and alarm time */ 

    Clock.time.hours = 0; 
    Clock.time.minutes = 0; 
    Clock.time.seconds = 0; 

    Clock.alarm_time.hours = 0; 
    Clock.alarm_time.minutes = 0; 
    Clock.alarm_time.seconds = 0; 
    
    /* alarm is not enabled */ 
    Clock.alarm_enabled = 0; 

    /* initialise semaphores */ 
    pthread_mutex_init(&Clock.mutex, NULL); 
    sem_init(&Clock.start_alarm, 0, 0); 
}

A function which sets the time can be written as

/* clock_set_time: set current time to hours, minutes and seconds */ 
void clock_set_time(int hours, int minutes, int seconds)
{
    pthread_mutex_lock(&Clock.mutex); 

    Clock.time.hours = hours; 
    Clock.time.minutes = minutes; 
    Clock.time.seconds = seconds; 

    pthread_mutex_unlock(&Clock.mutex); 
}

It is advantageous to place the above source code, which is associated with the clock, in a file clock.c. A header file clock.h can then be used, for the purpose of presenting an interface to the clock. The header file can be structured according to

#ifndef CLOCK_H
#define CLOCK_H

/* clock_init: initialise clock */ 
void clock_init(void); 

/* clock_set_time: set current time to hours, minutes and seconds */ 
void clock_set_time(int hours, int minutes, int seconds); 

#endif

where more functions can be added, as the program is developed. (Note that the Makefile needs to be modified as well when you add additional files to your project.)

The above use of struct results in a program module, which contains one clock. It is also possible to define an abstract data type, which allows a program which uses the module e.g. to define additional clocks. This programming methodology will also be used in Assignment 3 - Lift with Monitors.

According to the description in Section Communication with a graphical user interface, text strings are used for representation of information sent from the graphical user interface program to the real-time program.

It is possible to use the function sscanf, which becomes available by including stdio.h, for extraction of information from strings, e.g. according to

/* time_from_set_time_message: extract time from set time 
   message from user interface */ 
void time_from_set_time_message(
    char message[], int *hours, int *minutes, int *seconds)
{
    sscanf(message,"set %d %d %d", hours, minutes, seconds); 
}

Functions for string handling, which become available by including string.h could also be useful. Among these functions are e.g. strcmp, for string comparison, e.g. according to

        /* check if it is an exit message */ 
        else if (strcmp(message, "exit") == 0)
        {
            exit(0); 
        }

and strncmp, which can be used for checking a specificed number of characters in the beginning of a string, e.g. according to

        /* check if it is a set time message */ 
        if (strncmp(message, "set", 3) == 0)
        {
            time_from_set_time_message(message, &hours, &minutes, &seconds); 
            if (time_ok(hours, minutes, seconds))
            {
                clock_set_time(hours, minutes, seconds); 
            }
            else
            {
                si_ui_show_error("Illegal value for hours, minutes or seconds"); 
            }
        }

The functions sscanf, strcmp and strncmp belong to the C Standard Library. Here you can find more information about the C Standard Library.

Asymmetric synchronization

It is stated in the requirements that the thread which is responsible for repeating the alarm should not consume any CPU time when the alarm is not activated. This means that the alarm thread cannot for example repeatedly check whether the alarm should be activated (for example by constantly checking whether the alarm time is equal to the clock time (which by the way is not the purpose of the alarm thread) or checking that every 100ms or so. (The main reason why the polling based approach is a bad idea is because of power consumption, as the application will now need to regularly exit low-power mode to check whether the alarm should be activated or not.)

To solve this problem it is better if another synchronization method is used. In this assignment you should use asymmetric synchronization to communicate the activation of the alarm from the clock thread to the alarm thread. (However, you will still need to check whether the alarm has been deactivated in some way. For this purpose you should still use pthread mutexes.)

Finally, when introducing a semaphore in your lab skeleton you also need to ensure that the proper header files are included. Doublecheck that the semaphore.h file and the pthread.h file is included at the beginning of the clock.c file.

In summary

It is probably best to copy the file set_clock.c (from assignment 1) to clock.c for this assignment and remove the main function and threads from clock.c, and only keep the main function in set_clock.c from assignment 1 for this assignment, before adding new code for the various threads in set_clock.c.

Your solution should consist of the file set_clock.c (with the main function), the clock module (files clock.c and clock.h), the display module (files display.c and display.h), the communication module (files si_comm.c and si_comm.h) and the user inteface module (files si_ui.c and si_ui.h).

All these files need to be included on the dependency line in the Makefile.

Note: You do not need to use any mutexes or semaphores in the file set_clock.c since they should reside in the file clock.c. Also, all threads should reside within the file set_clock.c together with the main program.


Figure 1. All files and modules.


Informationsansvarig: Kent Palmkvist
Senast uppdaterad: 2021-11-14