Hardware代写:CSE237A Sensor Interaction And Real Time Scheduler

将8个传感器连接到Raspberry Pi 4,开发直接与它们交互的软件。

Raspberry Pi 4

Requirement

Many embedded systems have been designed to make diverse user-oriented devices. For example, to design LEDs reacting music and other sensors, the system has to monitor information from sensors and interact with actuators.

Sensing management should be executed efficiently as it is a time sensitive task. In our project we will be designing a sensor platform that emulates emergency detection in operating vehicles.

Your goal is to connect 8 sensors to RPi 4, develop software that directly interacts with them and a List scheduler that manages the sensing program in an energy efficient manner while not missing deadlines. These instructions explain the project part 2 in two sections: (i) Sensor interaction program implementation (ii) Energy-efficient List scheduler implementation.

Hardware and software requirements

  • Raspberry Pi 4
  • Intel ISA, 64 bit machine (x86_64)
  • Ubuntu 64bit 20.04 LTS desktop edition
    • You may run it as a virtual machine in VirtualBox.
    • If you are familiar with Linux and already have other Linux version (e.g., Ubuntu 16.04 LTS), you might be able to use it by adding needed repos/packages, however, we cannot support that.
  • ARM crosscompiler
  • Sensor kit: We will use 8 sensors from the sensor kit. The kit may include other sensors which have similar functionalities (e.g., Active buzzer vs. Passive buzzer), but you must use the same sensors specified in the next section.

We provide the skeleton code for this project on Canvas. Download “part2.zip” under Files > Projects > Individual Project > Part 2.

For this project you should follow the individual project part 1 instruction to install the custom kernel.

In normal operation, having multiple cores could offload some of the computation and improve performance. The Cortex-A72 of RPi 4 has 4 cores. By default, all cores are activated:

$ cat /sys/devices/system/cpu/online # should be 0-3
$ cat /sys/devices/system/cpu/offline # no output - nothing is offline

However, this can throw off our performance measurements if we’re not sure which core(s) jobs are running. Change the core usage settings so that your RPi 4 uses only a single core for this project. This allows us to execute programs on a single core and accurately measure performance of that core.

  • add “maxcpus=1” in /boot/cmdline.txt
    e.g., “dwc_otg, , rootwait, , plymouth.ignore-serial-consoles maxcpus=1”
  • reboot RPi
$ sudo reboot
  • Go to /sys/devices/system/cpu and read two files: “online” and “offline”. Each file shows available and unavailable CPUs
$ cat /sys/devices/system/cpu/online # should be 0
$ cat /sys/devices/system/cpu/offline # should be 1-3

Now your RPi 4 uses a single core.
Make sure that your RPi 4 can run your sample kernel module. Check if the program runs correctly using our closed-source script “part2/check_kernel_part2”.

$ chmod a+x ./check_kernel_part2
$ sudo ./check_kernel_part2
Check 1 online cores: 0 set governor: performance
Check 2 get scaling_cur_freq, value: 1500000
Check 3 get cpuinfo_cur_freq, value: 1500000 set governor: userspace
Check 4 set syscall max, return: 1500000
Check 5 set syscall min, return: 600000
Check 6 get scaling_cur_freq, value: 600000
Check 7 get cpuinfo_cur_freq, value: 600000
Check 8 set syscall min, return: 600000
Check 9 set syscall max, return: 1500000
Check 10 get scaling_cur_freq, value: 1500000
Check 11 get cpuinfo_cur_freq, value: 1500000
SUCCESS

If it prints “FAIL”, your kernel has not been updated correctly. Please make sure that you configure it to use a single core as detailed above.

Required sensors

  • Button Switch (KY-004)
  • 2-color DIP LED
  • Module (KY-011)
  • Metal Touch Sensor (KY-036)
  • Rotary Encoder (KY-040)
  • Passive Buzzer (KY-006)
  • Flashing LED Module (KY-034)
  • Tilt Sensor (KY-020)
  • 3-color SMD LED
  • Module (KY-009)

Section 1. Sensor interaction program implementation

We will be designing a system that will provide feedback via interactive LEDs. You will be able to turn on and off the system, and your code will monitor sensors and actuate the LEDs as described below:

  1. Once the program starts, the system is in the running state:
    • a. The auto-flash LED turns on.
    • b. The two-color DIP LED is
      • i. Green color if the metal touch sensor is not touched
      • ii. Red color if the metal touch sensor is touched
  2. The passive buzzer* beeps for 0.5 second if the tilt sensor detects a tilt
  3. SMD RGB LED state depends on both the rotary encoder and the obstacle avoidance sensor
  4. If the button is pushed in the running state, the system enters in the pause state:
    • a. The auto-flash LED turns off
    • b. Both the DIP and SMD LEDs turn off
  5. If the button is pushed in the pause state, the system reverts back to the running state, i.e., (1)

1.1 Interfacing individual sensors

The RPi 4 provides multiple General Purpose Input/Output (GPIO) connectors for sensors. You will connect the sensors to the GPIO pins, and implement a user-space program using WiringPi library which supports GPIO communication. It should be installed by default with Raspbian Buster. If not, get it using these instructions: http://wiringpi.com/download-and-install/

Then, you can check how each GPIO pin number corresponds to the WiringPi pin number as follows:

$ gpio readall

Note that the WiringPi (“wPi” column) numbers are different from the physical GPIO (“Physical” column) pin numbers.

If you get the following error, you need to upgrade your WiringPi for Raspberry Pi 4B:
Oops - unable to determine board type… model: 17

Complete the update with the following commands:

$ cd /tmp
$ wget https://project-downloads.drogon.net/wiringpi-latest.deb
$ sudo dpkg -i wiringpi-latest.deb

Use “gpio -v” to check the version of WiringPi and make sure it is higher than or equal to version 2.52.

Here is how you can test the sensors:

Step 1: Connect a sensor to proper GPIO pins

  • a. A sensor has at least 2 pins. For example, an auto-flash LED sensor has two pins, one for ground (0V) and the other for input signal (0/5V). Some sensors (e.g., buzzer) may have 3 pins, one for ground (0V), another one for 5V power, and the other one for an input signal (0/5V). For more detailed guidelines for pins, please refer to the included manual in the sensor kit, or by simply searching the sensor part number online.
  • b. Be careful to connect the power pins of sensors/RPi 4 (0/5V) correctly. You should turn off the RPi 4 to avoid any unexpected problems while working. Some sensors are sensitive to wrong voltage connections (e.g., 5V supply for a ground pin), and may break if mistakes are made while connected to power.
  • c. As long as the voltage requirements are met, you may use any of the GPIO pin assignments to connect your other sensors pins. In the next section (1.2), you will be required to use the given pin assignments (specified in “assignment1.h”).
  • d. The sensors of the Sunfounder kit already have required resistors. For example, an auto-flash LED sensor already has its own resistor to properly work.
  • e. The sensitivity of some sensors (e.g. microphone sensors) can be adjusted with a potentiometer on the sensor module. A red LED should be on when the sensor is powered on. A second LED will turn on when the sensing threshold is triggered. Decrease the triggering threshold by rotating the potentiometer in a clockwise direction.
  • f. The RPi 4 does not support analog inputs/outputs on their GPIO. To connect analog sensors you would need an ADC (analog-to-digital convertor). We do not cover analog sensing in this project.

Step 2: Implement a sensor test program in the following manner

  • a. Initialize sensor interfaces before reading/sending digital signals.
  • b. Access/control a PIN’s inputs and outputs. The following sample code “test.c” controls an LED.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
#include <wiringPi.h>
#include <stdio.h>

#define LEDPin 0
int main(void) {
if (wiringPiSetup() == -1) {
// if the wiringPi initialization fails, print error message
printf("setup wiringPi failed !");
return 1;
}

pinMode(LEDPin, OUTPUT);

while (1) {
digitalWrite(LEDPin, LOW); // led off
printf("led off...\n");
delay(500);
digitalWrite(LEDPin, HIGH); // led on
printf("...led on\n");
delay(500);
}
return 0;
}

Some hints:

1
2
softPwmCreate(PIN_RED, 0, 0xFF);
softPwmWrite(PIN_RED, 0xC8);
  • The millis() function in WiringPi can obtain a timestamp in milliseconds which can help you keep track of the elapsed time without calling delay/sleep functions (http://wiringpi.com/reference/timing/).
  • The rotary encoder uses two pins to determine the direction of rotation. Specifically, when pin A changes state, whether pin B’s output is equal to pin A indicates two rotating directions. Check the following tutorial for more details: https://lastminuteengineers.com/rotary-encoder-arduino-tutorial/. Debouncing is not required - you should be able to obtain stable direction detection if you rotate slowly.

Step 3: Build the sensor test program

Build option 1: Compile on RPi 4 with WiringPi
Use gcc with the WiringPi library on RPi 4:

$ gcc test.c -o test -lwiringPi

Build option 2: Cross-compile in Ubuntu with WiringPi
To cross-compile programs with the wiringPi library, you also need a cross-compiled version of WiringPi library.
We provide the precompiled library and header files for RPi 4 on Canvas.

  1. Place the downloaded directory “part2/wiringPi_armhf” in “~/RPdev/“ directory in your VM.
  2. Using the downloaded files, you can cross-compile the program that uses wiringPi. For example, the above example can be cross-compiled as follows (one line, with a space after -lwiringPi):
$ arm-linux-gnueabihf-gcc test.c -o test -lwiringPi
-I/home/YOUR_ACCOUNT/RPdev/wiringPi_armhf/ -L/home/YOUR_ACCOUNT/RPdev/wiringPi_armhf/

Step 4: Execute the test program(s)

Test each sensor prior to proceeding. Pay particular attention to the HIGH/LOW values described in https://tkkrlab.nl/wiki/Arduino_37_sensors for each sensor, as they dictate how to use it. To execute the program binary, you need the root privilege.

$ sudo ./test

1.2 Sensor interaction program (with skeleton code)

Implement the program based on a skeleton C code that we provide in “part2/section1”. It has 5 files:

File name Description
main_section1.c The main() function of the program. Do not modify it.
assignment1.h The source code you need to implement for sensor interactions.
assignment1.c You will modify and SUBMIT them.
Makefile.host A Makefile for cross compiling (You need to modify a path in the Makefile)
Makefile.rp A Makefile for native compile (i.e., on RPi 4)

If you are compiling on the RPi 4, rename “Makefile.rp” to “Makefile”:

$ cp Makefile.rp Makefile

If you are cross-compiling:

$ cp Makefile.host Makefile

After make, execute as follows:

$ sudo ./main_section1

Stop the program when it’s executing by pressing “Ctrl+c” in the terminal.

The code controls the 8 sensors using 8 different threads.

  1. The main() function of “main_section1.c” executes following steps:
    • a. Initialize wiringPi and call other initialization functions
    • b. Create 8 threads corresponded to each sensor
    • c. Wait until all threads are finished
    • d. Delay 10 milliseconds, and execute again the step (b) in a loop
  2. Pin assignments of the sensors are already specified in assignment1.h as macros. You must use these specified numbers, as this is how we test your code. (e.g., #define PIN_BUTTON 0)
  3. During initialization, it calls the init_shared_variable() and init_sensor() functions. For any other data initialization, you can implement the function bodies in assignment1.c. (e.g., Initial INPUT/OUTPUT mode of the WiringPi pins)
  4. Upon execution, each created thread will call the function, body_SENSORNAME(). You need to implement body_SENSORNAME() functions in assignment1.c.
  5. The “SharedVariable” argument is provided to init_shared_variable() and the 8 body_SENSORNAME() functions. This variable, a C structure, is shared across all function calls and threads. You can extend this data structure as needed.
  6. If you want to terminate the program by finishing the main loop, you can set the value of SharedVariable-]bProgramExit to 1. See “assignment1.c” for an existing example.

The following will ensure that your implementation can be graded correctly:

DOs

  1. Complete the following functions in “assignment1.c”. Implement each body_SENSORNAME() function to control only the corresponding sensor. For example, body_button() function has to access and control only the “button sensor” with PIN_BUTTON, not the “big audio sensor”.
1
2
3
4
5
6
7
8
9
10
void init_shared_variable(SharedVariable* sv);
void init_sensors(SharedVariable* sv);
void body_button(SharedVariable* sv); // Button
void body_big(SharedVariable* sv); // Big audio sensor
void body_encoder(SharedVariable* sv); // Rotary encoder
void body_obstacle(SharedVariable* sv); // Obstacle avoidance sensor
void body_twocolor(SharedVariable* sv); // DIP two-color LED
void body_rgbcolor(SharedVariable* sv); // SMD RGB LED
void body_aled(SharedVariable* sv); // Auto-flash LED
void body_buzzer(SharedVariable* sv); // Buzzer

Add more variables in the “SharedVariable” structure if needed.

DON’Ts

  1. Do not submit the main file (main_section1.c). This file should not be modified and your implementation must work correctly using the original file.
  2. Do not modify the WiringPi PIN numbers. Use the predefined pin numbers for the compatibility of other RPi 4 (we test your code on the TA’s RPi 4).
  3. Do not modify the given function declarations. For example, if you modify “void init_sensors(SharedVariable sv);” to “void init_sensors(SharedVariable sv, int SOME);”, it will not be correctly executed with the provided original main file.
  4. Do not implement any code that involves ANSI locking (e.g. pthread_mutex) and delay/sleep functions. This could potentially invalidate the scheduler project in Section 2.

Section 1 In-Person Checkpoint Demo

Demo your sensor interaction program to the TA. This is a sanity check for you, as your sensor interaction program will need to be correctly implemented to finish the project part 2. Bring to the demo:

  • Your RPi 4 with your working sensor interaction program
  • Your sensors, connected to your RPi 4 and ready to run with your program

Build and run your sensor program with an unmodified version of “main_section1.c”. The TA will test the various sensing and actuation scenarios.

Section 2. Energy-efficient List scheduler implementation

Implement an energy-efficient List real-time scheduler to run sensors and actuators along with workloads that have to be executed during sensor access (e.g. sensor ramp-up, preprocessing, etc.). In Project Part 1, you already observed that the CPU frequency can be used to lower the energy cost of workload execution.

2.1 Prepare your environment

  1. Verify your RPi 4 settings
    The RPi 4 must use the custom kernel from Project Part 1. The RPi 4 must use only a single core.
  2. Change default boot option to text console
    Since you will implement a real-time scheduler, your system should execute minimal additional processes. Since the window manager and graphical user interface (GUI) run a number of threads, you should limit the RPi 4 to terminal mode only. If you are using GUI as the default boot option, change the boot option to the text console using raspi-config:
    • $ sudo raspi-config
    • Select “System Options > Boot / Auto Login”
    • Select “Console”
    • Select “Finish” and reboot your system

If you want to start the GUI again:

$ startx

Then to return to text console, log out.
When executing the user-space scheduler program that you will implement, always use the text console.

2.2 Download base code and infrastructure for scheduler

The Linux system does not by default support any hard real-time scheduling mechanism. Instead, we provide the base code that can schedule threads in a user space program with frequency controls. Implement and submit four files “assignment1.h”, “assignment1.c”, “assignment2.h” and “assignment2.c”.

Step 1. Download the skeleton C code that we provide in “part2/section2”

We provided the precompiled “scheduler.o” and “workload.o” files as objects without the source.

We provided three standard workloads in directories “LOW”, “MED”, and “HIGH” for your local test set:

$ ls LOW MED HIGH
LOW: deadlines.c workload.o
MED: deadlines.c workload.o
HIGH: deadlines.c workload.o

As indicated by the directory’s name, workload LOW is the easiest; workload MED is of medium difficulty; and workload HIGH is the most challenging. Your code performance grade will be based on the ranking of your implementation as compared with everyone else in the class. The quality of your implementation depends on minimizing the number of missed deadlines and making the scheduler energy efficient. The scheduler that has no missed deadline and is the most energy efficient will get the highest score for this section. Missing deadlines will result in a lower grade; the implementations with a similar number of missed deadlines will be further compared in terms of the energy efficiency.

Your code will be run on the TA’s RPi and tested using standard workloads and two unknown workloads. The unknown workloads will not be more difficult than the HIGH workload.

Important: Your code performance grade will depend on

Step 2. Compile the provided source code

Compile the program using the provided Makefile. If you want to cross-compile, change the path in the Makefile appropriately.

$ make # or on a host: `make CROSS\_COMPILE=arm-linux-gnueabihf-`

You can see the three compiled binaries: main_section2_low, main_section2_med, and main_section2_high. They were compiled using the three different workloads. If you want to compile an individual binary, you can use:

$ make low
$ make med
$ make high

Step 3 (Optional)

If you would like to test more workloads, you can download additional workloads at this link.

You will need to input your PID and download “workload.zip”. The workload is automatically generated, so every student may get a different workload set. The additional workloads will be of similar difficulty as the standard workload 2 and 3.

2.3 Description of scheduler base code

Let’s take a look at how the provided code works. Using this, you can implement a soft real-time scheduler. In the same way you did in the sensor interaction program implementation, this program creates 8 threads for sensor interaction.

Worker thread and virtual workload

Since the actual interaction for each sensor of the sensor kit is typically executed in a very short time, we assume that there are 8 virtual workloads which precede each sensing interaction function. For example, if a sensor that needs a non-trivial access time, preprocessing, and setup/hold for ADC conversion. The provided base code mimics this concept, so the virtual workload, and subsequently, sensor access, may take hundreds of milliseconds to execute. Once each thread (which will be scheduled) is executed, “workload_SENSORNAME()” function and “body_SENSORNAME()” function are executed one by one. We will call this thread the worker thread.

  • The virtual workload is defined as “workload_SENSORNAME()” in “workload.h” and “workload.o”. The actual sensor interaction (i.e., “body_SENSORNAME()” function) will be based on your section 1 implementation.
  • Execution of the workloads need to satisfy the dependency graph, defined in “deadlines.c”.
  • For the actual implementation of the thread function, check out the “thread_def” macro in “main_section2.c”.

Scheduler thread

After initialization, the main thread of the base code plays a role for the scheduler thread. It calls three functions repeatedly in a loop. (See main() function in “main_section2.c” file.)

  1. prepare_tasks() (closed-source in “scheduler.o”)
    • a. This function is responsible for checking the period and creating the 8 worker threads.
      • i. The created worker thread is not executed until it is selected by select_task() function. (see below)
    • b. The deadline of each thread is the same as the period.
      • i. If all worker threads are executed completely, it waits until a worker thread’s execution is triggered again according to its period.
    • c. For your convenience, this function also checks and reports missing deadlines (if any)
      • i. It only reports the first 100 errors.
  2. select_task()
    • a. You NEED TO IMPLEMENT this function in assignment2.c.
    • b. It needs to implement a scheduling algorithm, and return a TaskSelection structure (defined in “scheduler.h”) including a selected task and a requested frequency.
  3. execute_task() (closed-source in “scheduler.o”)
    • a. Given the TaskSelection structure, this function checks task dependencies and reports dependency violations (if any).
    • b. If the selected worker thread is available and meets the dependency requirement, this function executes the selected worker thread at the requested frequency.

The selected worker thread is executed as follows:

  • If task dependencies are met and the worker thread is available, the thread selected by execute_task() is guaranteed to be executed for 10 milliseconds after scheduled, or until it finishes, whichever comes first. Once a worker thread is executed for 10 ms (or finishes), prepare_task() is executed again and another (or same) thread can be selected in select_task().
  • If the selected thread finishes execution within the allotted 10 milliseconds, it terminates immediately and goes back to the loop of the scheduler thread.

You also need to consider the following facts when you’re designing your scheduler:

  • Even if a worker thread misses its deadline, it is not terminated immediately but still schedulable. In this situation, the scheduler considers that the deadline is missed, and doesn’t create a new worker thread. You may want to schedule such threads with higher priority.
  • The closed-source part of prepare_task() and execute_task() is implemented so that they run in a very short time.
  • The worker threads may not be created exactly at their periods. They may have a small delay (at most 10 ms due to the allotted time slot). More specifically, the worker thread is only created when the scheduler thread is first encountered in prepare_task(). The real-time availability of the worker threads are recorded by aliveTasks array (see below).

Program execution with adjustable run time

When executing the program, you can adjust the running time of the experiment by the program parameter. If the time is not specified, the scheduler will be executed for 10 seconds.

$ sudo ./main_section2_low <time in seconds>

Note again that you should test other workloads, using “./main_section2_med” and “./main_section2_high”.

2.4 Energy-efficient List scheduler implementation

Implement an energy-efficient List scheduler in “assignment2.c” file, which considers periodic dependent tasks and uses a single core. To get full credit for the coding part, you need to implement a List scheduler using the length of the longest path that this task belongs to (i.e., the number of tasks on this longest path) as the priority metric. The longer the path, the higher the priority. For two tasks with the same priority, schedule the one with an earlier deadline. More sophisticated scheduler design is welcome for a better performance, as long as you explain the implementation details in your report.

You should implement two functions:

  1. void learn_workloads(struct shared_variable* sv);
    This function is called when initializing the program. It will be the location where you will initialize everything you need for your scheduler. More importantly, you can characterize the workload of each worker thread here, such as the execution time of each worker thread.
  • Parameters
    • The shared variable sv is the same one of Section 1. You may expand it for your purpose.
  • What you should know:
    • You may use the “get_current_time_us()” function defined in scheduler.h. It gives the microseconds since the epoch.
    • If required, you can reuse the PMU implementation from Project Part 1 here to profile the 8 worker threads with their workloads.
    • You may change the CPU frequency using “set_by_min_freq()” and “set_by_max_freq()”
      • They are defined in “governor.h/.c”.
      • To minimize the I/O time to change the frequency, “governor.c/.h” uses a custom system call that is part of your custom kernel.
    • The deadline (=scheduling period) of each worker thread is defined by workloadDeadlines in “deadlines.c” as an 8-element array. The deadlines are represented in microseconds.
      • The index of the array corresponding to each worker thread is defined in “workload.h”.
    • The task dependencies are defined by workloadDependencies in “deadlines.c” as an 8-by-8 matrix. “workloadDependencies[i][j] = 1” means that task i depends on task j, i.e., task j has to be finished before task i can be executed. Such dependencies are checked before executing the selected task in “execute_task()”.
    • There is no loop in the task dependencies, but there could be one task depending on multiple other tasks, or multiple tasks depending on a single other task.
    • If you want to estimate more accurate execution time in actual scheduling, you may need to consider the execution time of the scheduler thread as well.
  1. TaskSelection select_task(struct shared_variable sv, const int aliveTasks, long long idleTime);
    This function is called while the scheduler thread is executing. You need to implement the List scheduler with a frequency control. Your function eventually has to return a proper worker thread to be executed with a proper frequency using the TaskSelection structure. As a sample scheduler, we provide a naive round-robin scheduler in the skeleton code.
  • Parameters
    • The shared variable sv is the same one we used in Section 1.
    • aliveTasks is an 8-integer array which represents which worker threads are currently schedulable. If an element of the array is 1, it means the corresponding thread is still alive, so schedulable. Once a worker thread is finished, the element is set to 0.
      • For example, if BUTTON and BUZZER threads are still alive, then aliveTasks = {1, 0, 0, 0, 0, 0, 0, 1}.
      • The flipping history of aliveTasks indicates when one worker thread is finished or newly created. For example, when aliveTasks[i] switches from 1 (alive) to 0 (not alive), it means that task i is finished for the current round. When aliveTasks[i] switches from 0 to 1, it means that task i is newly created and needs to be scheduled before the next period ends.
    • idleTime gives a time duration in microseconds. When all tasks are completely scheduled, the scheduler thread (prepare_task() function) waits for the next period without any workload. This variable gives the idle time. You will need this variable to compute the energy consumption.
  • Returns
    • The return variable, TaskSelection structure, has two fields:
      • int task: Scheduled task (0~7: Index of the selected worker thread)
        • If you want to skip the current slot and stay idle, you can return -1 and execute_task() will directly move to the next loop.
      • int freq: Applied CPU frequency (0: Low frequency, 1: High frequency)
  • What you should know:
    • You may record the task status to help manage dependencies
      • Before executing the selected thread, execute_task() checks whether the required precedent tasks have been finished in the current round. If dependency is not satisfied, the worker thread will not be executed in “execute_task()” and an error will be reported. Once a worker thread finishes, the task dependency status is updated timely (closed-source in “scheduler.o”).
      • To meet the dependency, all precedent tasks need to be finished in the current round. In other words, the execution times of precedent tasks should be greater than the selected task.
    • You may use the “get_scheduler_elapsed_time_us()” function to get the time passed since each scheduling task began. (i.e., the timer is started from the first prepare_task() call.)
    • You don’t need to use the functions that control CPU frequency inside of this function.
      • The frequency is already changed by execute_task() function based on the returned TaskSelection structure. If you change it again inside this function, it may degrade the scheduler performance.
    • A rule of thumb is that you have to implement this function in a performance-efficient way. It’s a part of the scheduler thread, so any delay of this function consumes the scheduling times repeatedly.
    • Do not use any file IO or interruptible function call here.
      • It may result in unexpected scheduler behavior or system blocking. Please remember again that it’s a part of the scheduler which must be executed quickly.
    • To avoid IO operations, you must use the “printDBG()” function instead of “printf()” function.
      • The printDBG() function is provided as a part of the base code. (see scheduler.h)
      • The usage is the same as that of printf(). It stores the printed lines into a memory buffer, and the buffer is flushed when the program is finished.
      • The memory buffer size is initialized to 128 MB at default. You can adjust the buffer size in main_section2.c: init_deferred_buffer() function.

2.5 Report on scheduler design and results with actual sensor implementation

  • Briefly explain how you implemented the sensor interaction program in Section 1.
  • Carefully explain how you designed and implemented your energy efficient scheduler. You may want to clarify the following aspects:
    • How you implement learn_workloads()
    • How you implement select_task()
    • How you compute the weighted mobility metric
    • How you select the proper in the weighted mobility metric
    • How you make your scheduler energy-efficient
  • Provide a table for the three provided workloads. The table should contain the estimated energy and the result if the scheduler was missing the deadline or not.

Use the table below to calculate the CPU energy values:

CPU Frequency Power (Total when active) Power (Idle)
1.5 GHz 1400 mW 50 mW
600 MHz 500 mW 50 mW

The active power is the total power consumption while any workload is working. The idle power is the power consumption while no workload is working. The idle time is provided in the third parameter of the select_task() function.

What to do for common error messages (in particular “file not found” / “command not found”)

  • Google your error first
  • Are you on the right machine?
  • Are you in the right directory?
  • Is your PATH variable set correctly? (In ~/.bashrc?)
  • Did you need sudo?
  • If you’re trying to run a binary - is it executable?
  • If you’re trying to access an external device - is your external drive mounted correctly?
  • Do you have the right packages/dependencies? (Especially if it’s a *.h file that’s missing)

Asking for help on Piazza:

  • The best posts are public, including your last commands, the full error message, and a description of your hardware/OS environment. “It does not work” is not a sufficient post for us to help you. You must state clearly what you tried, and what indicates a failure (e.g. exact error message, LED lights, blank screen, etc).
  • If someone makes a suggestion that works for you, please report back!
  • If you figure it out on your own, please report back!
  • “I solved it” or “I made a mistake” is usually not a helpful end to the discussion. Don’t be embarrassed by your simple mistakes - we all make them. Please describe the solution and help the next person out!

Individual Project Submission Instructions

Submit the following via Gradescope:

  • Submit the four files “assignment1.c, assignment1.h, assignment2.c, and assignment2.h”
    • Don’t submit other source files.
    • Your code must be executed correctly using the predefined wiringPi PIN numbers and the provided other files.
    • Make sure to comment out any printf/printDBG/delay functions during debugging in all submitted, as your code will be tested directly on the TA’s RPi4 for performance evaluation.
  • Report
    • Maximum 3pgs, 12pt Times New Roman font, excluding figures and table.
    • Briefly explain how you implemented the sensor interaction program in Section 1
    • Carefully explain how you designed your energy efficient scheduler
    • Provide a table for the three provided workloads. The table should contain the estimated energy and the result if the scheduler was missing the deadline or not
    • Do not include your source code in the report.