Termbank
  1. A
    1. Abstraction
    2. Alias
    3. Argument
    4. Array
  2. B
    1. Binary code file
    2. Binary number
    3. Bit
    4. Bitwise negation
    5. Bitwise operation
    6. Byte
  3. C
    1. C library
    2. C-function
    3. C-variable
    4. Character
    5. Code block
    6. Comment
    7. Compiler
    8. Complement
    9. Conditional statement
    10. Conditional structure
    11. Control structure
  4. D
    1. Data structure
    2. Duck typing
  5. E
    1. Error message
    2. Exception
  6. F
    1. Flag
    2. Float
  7. H
    1. Header file
    2. Headers
    3. Hexadecimal
  8. I
    1. Immutable
    2. Initialization
    3. Instruction
    4. Integer
    5. Interpreter
    6. Introduction
    7. Iteroitava
  9. K
    1. Keyword
  10. L
    1. Library
    2. Logical operation
  11. M
    1. Machine language
    2. Macro
    3. Main function
    4. Memory
    5. Method
  12. O
    1. Object
    2. Optimization
  13. P
    1. Parameter
    2. Placeholder
    3. Pointer
    4. Precompiler
    5. Precompiler directive
    6. Prototype
    7. Python console
    8. Python format
    9. Python function
    10. Python import
    11. Python list
    12. Python main program
    13. Python variable
    14. Python-for
    15. Pääfunktio
    16. printf
  14. R
    1. Resource
    2. Return value
  15. S
    1. Statement
    2. Static typing
    3. String
    4. Syntax
  16. T
    1. Terminal
    2. Type
    3. Typecast
  17. U
    1. Unsigned
  18. V
    1. Value
  19. W
    1. Warning
    2. while
Completed: / exercises

Interrupts

Learning goals: Interrupts and their usage in an embedded device
In a computer, an interrupt is an internal or external signal for a processor, that literally interrupts other functions of the processor. When an interruption occurs, the processor saves its state (state of the current program, registers, etc.) and starts the execution of the handler routine associated with the interruption. When the handler has been executed, the processor loads back to its previous state, and continues to execute the program precisely where it left off.
In the picture below is the pin layout of ATmel's ATtiny2313 microcontroller, where the red markings indicate an external hardware interruption pin (INT0 and INT1). We could connect the outgoing interrupt line of some peripheral device to these pins, and this way capture the interruption signals sent by the device. In microcontrollers, there are often pins for external interruptions connected directly to the interruption handling logic of the circuit. Naturally, the possible interruptions that a certain peripheral device / component is able to produce, are carefully defined in datasheets.
"External interruption pins"
There are two types of interruptions. An interrupt can be hardware-based (internal to the main unit or coming from a peripheral device), that is typically caused by an asynchronous (independent of the scheduling of program) event inside of the processor or in a peripheral device. As a result, an interrupt occurs unexpectedtly from the perspective of the program being run, at any time. Hardware interruptions are caused inside of the main unit by for example dividing by zero (error) or externally by a message from a peripheral device that tells the processor that it needs attention because there is new data available or an error has occured.
A software interruption can be caused by a special machine instruction, for example INT-instruction in Intel's x86 processors. A software interruption triggers an interruption signal in the main unit, that is handled in the operating system / firmware in a similar manner as a hardware interruption, and consequently a handler function defined for this interruption is executed. Some timers can generate this type of interrupts.
Interruptions have some priority. An interruption with a higher priority is executed first. Priorities matter when multiple interruptions occur at the same time, or when a lower priority interruption is being executed. In a situation like this, the interruption with a higher priority stops the execution of the handler routine with a lower priority, and executes its own handler routine before giving the control of the system back. For example, in embedded systems, the hardware interruptions originating from a RESET-pin (RUN in pico) have the highest priority. The second highest priority interruptions are hardware interruptions, because they are typically time-critical. The interruptions with the lowest priority are software interruptions. A programmer can set a priority for a software interruption, and they are executed according to the programmers will (they can also override each other).
The handler routine (also handler) of an interruption is almost like a function, but it is never called from the program being executed. This is important to know, because before an interruption handler can be executed, the state of the processor needs to be saved, and by calling the handler as a function, this operation would not occur. In addition to this, the routine does not return any value, but global variables and registers can be used inside of it to deliver information. Now, in C the handlers are implemented as functions, and we need to tell that those functions are actually handlers to FreeRTOS and Pico SDK.
Interruption handlers are time critical for two reasons: they stop the program being executed currently and they can override each other. Because of this, the execution of the entire program is easy to block/ prevent (by mistake) with a higher priority interrupt handler, which is taking too much time to execute. Now, RTOS instructs that a hardware interrupt should not take more than 5 microseconds and for software interrupt, the same metric is 100 microseconds. In practice, only minimal amount of code should be executed in a handler. Because of this, handlers usually just modify the values of registers or global state variables. For example, it is a good practice that from a handler we inform the program that there is new data availabe in a peripheral device, and then read the data once the handler has run. Again, one of these golden tips. Well, more on this in the Finite state machines-material...

Interrupts in the Raspberry Pi Pico

The Pico supports interrupts for all its peripherals. For example, timers, UART, I2C, SPI, DMA, PIO and others can generate an interrupt signal when an event occurs. These interruptions are managed by the Cortex-M0+ NVIC (Nested Vectored Interrupt Controller). In the Pico SDK this is exposed through the hardware_irq package. With these functions you can:
For example:
irq_set_exclusive_handler(TIMER_IRQ_0, my_timer_handler);
irq_set_priority(TIMER_IRQ_0, 0);
irq_set_enabled(TIMER_IRQ_0, true);
Special case with GPIOS: All the GPIO pins are connected to a single interrupt line per core (SIO_IRQ_PROC0 and SIO_IRQ_PROC1). This means that, at the hardware level, the processor only sees 'a GPIO interrupt happened. The Pico SDK provides some magic (aka extra logic) that reads which pin triggered the event and then calls the adequate handler. In this way you can easily associate an interrupt handler to each GPIO, even if the hardware only exposes one shared line.

GPIO interrupts

In a previous chapter we presented an interruption that reacts to a state change of a pin. Let's get back to it.
...
// Interruption handler
void buttonFxn(uint gpio, uint32_t eventMask) {
    ...
}

int main() {
    ...
    // We assign the interrupt handler buttonFxn to a pin
    gpio_set_irq_enabled_with_callback(BUTTON1, GPIO_IRQ_EDGE_FALL, true, buttonFxn);
    ...
}
We can see that in the function, the constant GPIO_IRQ_EDGE_FALL has been assigned to the interruption. Here the interruption will occur, when the state of the pin changes on falling edge, or in other words in transition HIGH (operating voltage) -> LOW (ground). A pin interruption can also be set to occur on rising edge, which means the state transition LOW -> HIGH, with the constant GPIO_IRQ_EDGE_RISE.
A handler for the interruption signal is set with the last argument of the function call gpio_set_irq_enabled_with_callback, which here is the function buttonFxn. The interruption-specific functionality is then implemented inside of this function.
The third parameters (bool) indicates if we should activate or deactivate the interrupt.

Peripheral device interrupts

As an example of peripheral device interrupt in Pico, we introduce the multi-functional ICM-42670-sensor. Integrated to this single sensor, we have gyroscope and an accelerometer.
We can use the interruption functionality of ICM-42670, because its external interruption line has been connected to Pico, ICM42670_INT (in header file pins.h). So, the interrupt is enabled in a similar manner as the pin interrupt above. The ICM-42670 can trigger an interrupt in different circunstances that can be programmed by the user: for instance, when it has capture a determined number of samples, when the gyroscope detects certain inclination or when the pedometer integrated in the chip detect a step.
In the following example we will learn how to enable / disable external interrupts, coming from the ICM42670.
void ICMFxn(uint gpio, uint32_t event_mask) {
    ...
}

void sensorTask(void *pvParameters) {
    // Allowing interrupts from ICM-42670 in rising edge
    gpio_set_irq_enabled(ICM42670_INT, GPIO_IRQ_EDGE_RISE, true);
    // Disallowing interrupts
    gpio_set_irq_enabled(ICM42670_INT, GPIO_IRQ_EDGE_RISE, false);
}

int main(void) {
...
// Enabling ICM-42670 interrupt pin
gpio_init(ICM42670_INT);
gpio_set_dir(ICM42670_INT, GPIO_IN);
// Setting an interrupt handler function
gpio_set_irq_enabled_with_callback(ICM42670_INT, GPIO_IRQ_EDGE_RISE, false, ICMFxn);
...
}
Let's review this example step by step. Now in function main we enable the interrupt line in our program and assign the interrupt handler function ICMFxn.
When setting the interrupt handler for the function gpio_set_irq_enabled_with_callback, the third argument is given the value false. This means that interrupts are initially disabled. When we want to receive interrupts, for example within a task, we can use the Pico SDK function gpio_set_irq_enabled. For this function, we specify the desired interrupt pin, determine when the interrupt should be handled (using constants GPIO_IRQ_EDGE_RISE or GPIO_IRQ_EDGE_FALL), and enable or disable the interrupt with the values true or false. Typically, it’s best to enable interrupts at the last possible moment in the code to avoid disrupting program execution before we actually need them.
In the example, the implementation of sensorTask also includes enabling interrupts using the gpio_set_irq_enabled function. By toggling interrupts on and off programmatically, we can control their behavior.

Multiple GPIO interrupt sources

Sometimes we need to connect multiple GPIOs that can generate interrupts, for example when using buttons or sensors.
Let us imagine that we have two buttons, BUTTON1 and BUTTON2. Pressing each button should activate a different function: in our case BUTTON1 will turn on LED1 and BUTTON2 will turn it off.
There are two ways to implement this behaviour. The first approach is to use a generic callback that will be called for all GPIO events. In this case the SDK provides us with the information of which GPIO triggered the interruption and what event (rising or falling edge, level high or low) occurred. Inside this generic function we can then distinguish which button was pressed and act accordingly.
#include "pico/stdlib.h"
#include "hardware/gpio.h"

static void gpio_callback(uint gpio, uint32_t events) {
    if (gpio == BUTTON1 && (events & GPIO_IRQ_EDGE_FALL)) {
        gpio_put(LED1, true);   // turn on LED
    }
    if (gpio == BUTTON2 && (events & GPIO_IRQ_EDGE_FALL)) {
        gpio_put(LED1, false);  // turn off LED
    }
}

int main() {
    stdio_init_all();
    gpio_init(LED1);
    gpio_set_dir(LED1, GPIO_OUT);

    gpio_init(BUTTON1); 
    gpio_init(BUTTON2); 

    // Register the generic callback
    gpio_set_irq_enabled_with_callback(BUTTON1, GPIO_IRQ_EDGE_FALL, true, &gpio_callback);
    gpio_set_irq_enabled(BUTTON2, GPIO_IRQ_EDGE_FALL, true);

    while (1) { tight_loop_contents(); }
}
The second approach is to give each button its own raw interrupt handler by using gpio_add_raw_irq_handler.
This method bypasses the generic callback mechanism and lets us assign a dedicated function to each GPIO pin. In this case, the handler should explicitly communicate that it has process the interrutption using gpio_acknowledge_irq(uint gpio, uint32_t event_mask)
#include "pico/stdlib.h"
#include "hardware/gpio.h"
#include "hardware/irq.h"


static void button1_handler(void) {
    if (gpio_get_irq_event_mask(BUTTON1) & GPIO_IRQ_EDGE_FALL) {
        gpio_acknowledge_irq(BUTTON1, GPIO_IRQ_EDGE_FALL);
        gpio_put(LED1, true);   // turn on LED
    }
}

static void button2_handler(void) {
    if (gpio_get_irq_event_mask(BUTTON2) & GPIO_IRQ_EDGE_FALL) {
        gpio_acknowledge_irq(BUTTON2, GPIO_IRQ_EDGE_FALL);
        gpio_put(LED1, false);  // turn off LED
    }
}

int main() {
    stdio_init_all();
    gpio_init(LED1);
    gpio_set_dir(LED1, GPIO_OUT);

    gpio_init(BUTTON1); 
    gpio_init(BUTTON2); 

    // Register dedicated handlers
    gpio_add_raw_irq_handler(BUTTON1, button1_handler);
    gpio_add_raw_irq_handler(BUTTON2, button2_handler);

    gpio_set_irq_enabled(BUTTON1, GPIO_IRQ_EDGE_FALL, true);
    gpio_set_irq_enabled(BUTTON2, GPIO_IRQ_EDGE_FALL, true);

    //irq_set_enabled(SIO_IRQ_PROC0, true);

    while (1) { tight_loop_contents(); }
}
One important point is that the maximum number of handlers that can be associated with the GPIO is PICO_MAX_IRQ_SHARED_HANDLERS.
In practice, these functions call irq_add_shared_handler internally, and that is what imposes this limitation.

Timers

FreeRTOS also provides us timers in the timers.h-library, which we can use to implement timed events, or in other words, interruptions in fixed rates. For example, with the help of a timer, we could read sensor data, communicate with a peripheral device or blink a LED once per second.
There are two different type of timers: those which repeat the same function periodically after a given time and "one-shot" timers, those that only exeucte once
"Two different types of timers of RTOS"
An example tells us more than a thousand words, so lets go!!!
...
#include <timers.h>
...
void timerFxn(TimerHandle_t timer) {
    printf("%li\n", (xTaskGetTickCountFromISR() / portTICK_PERIOD_MS));
}

int main(void) {
    stdio_init_all();
    TimerHandle_t timerHandle;
    // Initializing a timer
    timerHandle = xTimerCreate(
        "Timer",              // Name of the timer
        pdMS_TO_TICKS(1000),  // Time period
        pdTRUE,               // We want to automatically restart this timer
        (void *) 0,           // Identification number of this timer
        timerFxn              // Timer function
    );
    if(timerHandle == NULL) {
        printf("Timer create failed\n");
        return 0;
    }
    else {
        // Starting the timer
        if(xTimerStart(timerHandle, 0) != pdPASS) {
            printf("Timer start failed\n");
            return 0;
        }
    }
    vTaskStartScheduler();
    return 0;
}
You may observe some similarities between this code and the creation of tasks that was covered earlier. At first, we create a handle (TimerHandle_t) for our timer, which we can use to manage this timer later in the program. With the help of a handle, we can also later check if our timer was created successfully.
Next, we will attach a new timer to our handle, which is first created with the function xTimerCreate. This function takes in as arguments the name of the timer, which does not really matter for the RTOS, but it can help us humans to identify the timer for example in the debugger. As a second argument to the timer we give it its time period. This is the time that the timer waits before it calls the function that was given. The third argument given to the timer tells the RTOS if the timer needs to be restarted at the end of its period. Here, the value pdTRUE means that the restart is desired. The value pdFALSE would mean that the timer is "one-shot", so it would be executed only once. Well, these are RTOS definitions again.
The second-to last argument given to the timer is its number, which we can use to distinguish between different timers at the program-level. If we set up multiple timers to call the same function with different time intervals, with this identifier we could distinguish between those timers. As the last argument, we set the timer function, that this timer calls at the end of its period. Observe, how the timer function has to take in as argument the handle of the timer. With this handle we can get information about the timer inside of the function, that triggered the execution of this function. (For example, inside of the timer function we could call the function pvTimerGetTimerID and we would get the identification number we set for this timer)
When the timer has been created, we should check that no errors have occured. In the example, we first check that the value of the handle is not NULL at this point. If it is, the creation of the timer failed, and we print this information to the terminal-window, after which we exit the program. Another check is performed when starting the timer. The starting is done with the function xTimerStart, which is given the handle of the timer to be started, and the amount of RTOS ticks to wait before starting the timer. In this case we don't want to wait at all, so we set 0 as the wait value. If the return value of xTimerStart is not the RTOS constant pdPASS, starting of the timer did not succeed. In this case we will inform the terminal window about this and exit the program.
We also notice that generally the constant pdPASS is used as FreeRTOS's way of telling that some operation was successful, and the constants pdTRUE and pdFALSE are available for the programmer to use when setting desired and undesired setting values.
With this library, we can create multiple concurrent timers to our program, we just need to introduce one handle per timer and settings arguments for the function xTimerCreate, and start each of the timers with the function xTimerStart. Well, this is not the best way to do things, as we can have only one timer interrupt, where we calculate multiple time periods.

Running a task periodically with vTaskDelayUntil

Sometimes we want a task to run in a strict periodic way, for example every 250 ms, always keeping the same rhythm. For that purpose FreeRTOS gives us the function vTaskDelayUntil().
This function is similar to vTaskDelay(), but with one important difference: instead of saying "wait X ticks starting from now", it says "wake me up exactly at this point of time, no matter when vTaskDelayUntil() was called". That way, if the task is supposed to run every 250 ms, it will keep that exact rhythm even if the work inside the task takes a bit longer sometimes. In other words, it guarantees a steady and predictable pace.
In order to really run periodically, this task usually needs to have a higher priority than other tasks. Otherwise, even if we ask it to wake up every 250 ms, it will only get CPU time when the scheduler decides. If other tasks with the same or higher priority are running, our periodic task will need to wait.
void vTaskFunction( void * pvParameters )
{
    char * pcTaskName;
    TickType_t xLastWakeTime;

    // The task name is passed in as a parameter
    pcTaskName = ( char * ) pvParameters;

    // Initialize xLastWakeTime with the current tick count
    // This is the ONLY place we assign a value to it
    xLastWakeTime = xTaskGetTickCount();

    for( ;; )
    {
        // Print out the name of this task
        vPrintLine( pcTaskName );

        // Delay task until the next 250 ms period
        vTaskDelayUntil( &xLastWakeTime, pdMS_TO_TICKS( 250 ) );
    }
}
A few important things to notice here:
So, with this mechanism we can make a task run at a precise interval, something very important in many real-time applications like sampling sensors or blinking LEDs with constant rhythm.

Timers in Pico

Although we recommend to write timers using FreeRTOS (because then they are fully integrated into the task scheduling),
it is also possible to use the Pico’s own timer system directly.
The RP2040 chip has a hardware timer and an alarm unit that can be accessed through the pico_time library.
The idea is that you can schedule a callback to run after some milliseconds. If the callback returns 0 it is only executed once, but if it returns a non-zero value it will be rescheduled and run again, effectively creating a repeating timer.
Let’s start with one example of a one-shot alarm that prints a message after one second:
#include "pico/stdlib.h"
#include "pico/time.h"
#include 

int64_t one_shot_callback(alarm_id_t id, void *user_data) {
    printf("One-shot alarm fired!\n");
    return 0; // 0 means do not repeat
}

int main() {
    stdio_init_all();

    // Schedule a callback 1000 ms (1 second) from now
    add_alarm_in_ms(1000, one_shot_callback, NULL, false);

    while (1) {
        tight_loop_contents();
    }
}
Easy, isn’t it?
Here the function add_alarm_in_ms() has four parameters:
The callback function itself must have the form int64_t callback(alarm_id_t id, void *user_data).
The parameter id identifies which alarm triggered (useful if you have several alarms), and user_data is the same pointer you passed when you created the alarm. The return value is crucial: returning 0 stops the alarm (one-shot), while returning a positive number schedules the next call after that many milliseconds.
But you can also have repeating alarms. In this case the callback simply returns the interval in milliseconds for the next call. For example, this alarm will print a message every 500 ms:
#include "pico/stdlib.h"
#include "pico/time.h"
#include 

int64_t repeating_callback(alarm_id_t id, void *user_data) {
    printf("Repeating alarm: 500 ms passed\n");
    return 500; // return next interval in milliseconds
}

int main() {
    stdio_init_all();

    // Schedule first alarm in 500 ms
    add_alarm_in_ms(500, repeating_callback, NULL, false);

    while (1) {
        tight_loop_contents();
    }
}
As you can see, Pico timers are powerful and allow very fine-grained control, but the code can quickly become more complicated to manage than using FreeRTOS timers. Still, it is useful to know that this option exists when you need precise alarms closer to the hardware and not trusting that much in FreeRTOS tasks.

Real-time clock

On top of all this, the Pico SDK provides us with hardware_rtc-library for real-time clock, that runs in the same time as us humans. The only downside of this is that we need to initialize the clock ourselves by setting it to right time before using it.
Again, we will trust in the power of an example.
...
#include <hardware/rtc.h>
...
// Timer function
void timerFxn(TimerHandle_t timer) {
   // Let's see the time from clock...
    datetime_t aika;
    while (1){
        rtc_get_datetime(&aika);
        // ...and print it out
        printf("Time is %02d:%02d:%02d\n", aika.hour, aika.min, aika.sec);
        // (en) Politely sleeping for a moment
        vTaskDelay(1000 / portTICK_PERIOD_MS);
    }
}

int main(void) {
    stdio_init_all();
    // We create a timer that automatically restarts and calls timerFxn approximately once per second
    // We can do this based on the previous example
    TimerHandle_t timerHandle;
    timerHandle = xTimerCreate(
        "Timer",
        pdMS_TO_TICKS(1000),
        pdTRUE,
        (void *) 0,
        timerFxn
    );
    if(timerHandle == NULL) {
        printf("Timer create failed\n");
        return 0;
    }
    else {
        if(xTimerStart(timerHandle, 0) != pdPASS) {
            printf("Timer start failed\n");
            return 0;
        }
    }

   // Initializing the time
    datetime_t aika = {
    .year = 2024,
    .month = 06,
    .day = 20,
    .dotw = 4,
    .hour = 11,
    .min = 59,
    .sec = 59
    };

   // Initializing the clock and setting it to right time
    rtc_init();
    rtc_set_datetime(&aika);

    vTaskStartScheduler();
    return 0;
}
In main-function we create the timer, which we have already familiarized ourselves with. In this example we use the timer to see the time from the real-time clock with Pico SDK's function rtc_get_datetime and to print it to the terminal window. You may also notice that the timer function here is too slow because of the printf function call. This could be fixed by for example using a print task along with a state machine and a global variable that stores the time read from the clock.
Before starting the program, we fill the desired start time for our clock by using the structure datetime_t, and give it to the function rtc_set_datetime, of course after initializing the onboard real-time clock circuit with the function rtc_init.

Interruptions in FreeRTOS

When we work with interrupts inside FreeRTOS we need to be careful: not all API functions can be called from an interrupt service routine (ISR). Many FreeRTOS functions assume they are called from a task and might try to block that task if a resource is not available. But inside an ISR there is no calling task to block!
That is why FreeRTOS provides special versions of some functions, with the suffix FromISR, that are safe to use inside an interrupt.
The rule is simple: never call a FreeRTOS API function from an ISR unless its name ends with FromISR. For example, if we want to start or reset a software timer from inside an interrupt, we should call xTimerStartFromISR() or xTimerResetFromISR().
If we want to resume a suspended task from an interrupt, we use vTaskResumeFromISR(). These versions are lightweight and avoid the checks needed for task context, making both the ISRs and the kernel more efficient.
For instance, if an interrupt should restart a timer (for example to measure inactivity), we could write:
void gpio_irq_handler(uint gpio, uint32_t events) {
    BaseType_t xHigherPriorityTaskWoken = pdFALSE;
    xTimerResetFromISR(myTimerHandle, &xHigherPriorityTaskWoken);
    portYIELD_FROM_ISR(xHigherPriorityTaskWoken);
}
These interrupt-safe APIs make it possible to connect the hardware world (interrupts) with the FreeRTOS world (tasks and timers) without breaking the scheduler.

To conclude

RTOS abstracts the usage of interrupts into fairly simple process, which involves writing the handler as a function. In fact, we don't even know if the library works based on interruptions or is it just a program function. To gain this knowledge, it might be beneficial to read the documentation of the library to know what is really going on. If we don't know, the handler we implement for the interruption might be too heavy.
?
Abstraction is a process through which raw machine language instructions are "hidden" underneath the statements of a higher level programming language. Abstraction level determines how extensive the hiding is - the higher the abstraction level, the more difficult it is to exactly say how a complex statement will be turned into machine language instructions. For instance, the abstraction level of Python is much higher than that of C (in fact, Python has been made with C).
Alias is a directive for the precompiler that substitus a string with another string whenever encountered. In it's basic form it's comparable to the replace operation in a text editor. Aliases are define with the #define directeve, e.g. #define PI 3.1416
Argument is the name for values that are given to functions when they are called. Arguments are stored into parameters when inside the function, although in C both sides are often called just arguments. For example in printf("%c", character); there are two arguments: "%c" format template and the contents of the character variable.
Array is a common structure in programming languages that contains multiple values of (usually) the same type. Arrays in C are static - their size must be defined when they are introduced and it cannot change. C arrays can only contain values of one type (also defined when introduced).
Binary code file is a file that contains machine language instructions in binary format. They are meant to be read only by machines. Typically if you attempt to open a binary file in a text editor, you'll see just a mess of random characters as the editor is attempting to decode the bits into characters. Most editors will also warn that the file is binary.
Binary number is a number made of bits, i.e. digits 0 and 1. This makes it a base 2 number system.
A bit is the smallest unit of information. It can have exactly two values: 0 and 1. Inside the computer everything happens with bits. Typically the memory contains bitstrings that are made of multiple bits.
Bitwise negation is an operation where each bit of a binary number is negated so that zeros become ones and vice versa. The operator is ~.
Bitwise operations are a class of operations with the common feature that they manipulate individual bits. For example bitwise negation reverses each bit. Some operations take place between two binary values so that bits in the same position affect each other. These operations include and (&), or (|) and xor (^). There's also shift operations (<< and >>) where the bits of one binary number are shifted to the left or right N steps.
Byte is the size of one memory slot - typically 8 bits. It is the smallest unit of information that can be addressed from the computer's memory. The sizes of variable types are defined as bytes.
External code in C is placed in libraries from which they can be taken to use with the #include directive. C has its own standard libraries, and other libraries can also be included. However any non-standard libraries must be declared to the compiler. Typically a library is made of its source code file (.c) and header file (.h) which includes function prototypes etc.
Functions in C are more static than their Python counterparts. A function in C can only have ne return value and its type must be predefined. Likewise the types of all parameers must be defined. When a function is called, the values of arguments are copied into memory reserved for the function parameters. Therefore functions always handle values that are separate from the values handled by the coe that called them.
C variables are statically typed, which means their type is defined as the variable is introduced. In addition, C variables are tied to their memory area. The type of a variable cannot be changed.
Character is a single character, referred in C as char. It can be interpreted as an ASCII character but can also be used as an integer as it is the smallest integer that can be stored in memory. It's exactly 1 byte. A character is marked with single quotes, e.g. 'c'.
Code block is a group of code lines that are in the same context. For instance, in a conditional structure each condtion contains its own code block. Likewise the contents of a function are in their own code block. Code blocks can contain other code blocks. Python uses indentation to separate code blocks from each other. C uses curly braces to mark the beginning and end of a code block.
Comments are text in code files that are not part of the program. Each language has its own way of marking comments. Python uses the # character, C the more standard //. In C it's also possible to mark multiple lines as comments by placing them between /* and */.
A compiler is a program that transforms C source code into a binary file containing machine language instructions that can be executed by the computer's processor. The compiler also examines the source code and informs the user about any errors or potential issues in the code (warnings). The compiler's behavior can be altered with numerous flags.
Complement is a way to represent negative numbers, used typically in computers. The sign of a number is changed by flipping all its bits. In two's complement which is used in this course, 1 is added to the result after flipping.
Conditional statement is (usually) a line of code that defined a single condition, followed by a code block delimited by curly braces that is entered if the condition evaluates as true. Conditional statements are if statements that can also be present with the else keyword as else if. A set of conditional statements linked together by else keywords are called conditional structures.
Conditional structure is a control structure consisting of one or more conditional statements. Most contrl structures contain at least two branches: if and else. Between these two there can also be any number of else if statements. It is however also possible to have just a single if statement. Each branch in a conditional structure cotains executable code enclosed within a block. Only one branch of the structure is ever entered - with overlapping conditions the first one that matches is selected.
Control structures are code structures that somehow alter the program's control flow. Conditional structures and loops belong to this category. Exception handling can also be considered as a form of control structure.
Data structure is a comman name for collection that contain multiple values. In Python these include lists, tuples and dictionaries. In C the most common data structures are arrays and structs.
Python's way of treating variable values is called dynamic typing aka duck typing. The latter comes from the saying "if it swims like a duck, walks like a duck and quacks like a duck, it is a duck". In other words, the validity of a value is determined by its properties in a case-by-case fashion rather than its type.
An error message is given by the computer when something goes wrong while running or compiling a program. Typically it contains information about the problem that was encountered and its location in the source code.
An exception is what happens when a program encounters an error. Exceptions have type (e.g. TypeError) that can be used in exception handling within the program, and also as information when debugging. Typically exceptions also include textual description of the problem.
Flags are used when executing programs from the command line interface. Flags are options that define how the program behaves. Usually a flag is a single character prefixed with a single dash (e.g. -o) or a word (or multiple words connected with dashes) prefixed with two dashes (e.g. --system. Some flags are Boolean flags which means they are either on (if present) or off (if not present). Other flags take a parameter which is typically put after the flag separated either by a space or = character (e.g. -o hemulen.exe.
Floating point numbers are an approximation of decimal numbers that are used by computers. Due to their archicture computers aren't able to process real decimal numbers, so they use floats instead. Sometimes the imprecision of floats can cause rounding errors - this is good to keep in mind. In C there are two kinds of floating point numbers: float and double, where the latter has twice the number of bits.
Header files use the .h extension, and they contain the headers (function prototypes, type definitions etc.) for a .c file with the same name.
Headers in C are used to indicate what is in the code file. This includes things like function prototypes. Other typical content for headers are definition of types (structs etc.) and constants. Headers can be at the beginning of the code file, but more often - especially for libraries - they are in placed in a separate header (.h) file.
Hexadecimal numbers are base 16 numbers that are used particularly to represent memory addresses and the binary contents of memory. A hexadecimal number is typically prefixed with 0x. They use the letters A-F to represent digits 10 to 15. Hexadecimals are used because each digit represents exactly 4 bits which makes transformation to binary and back easy.
In Python objects were categorized into mutable and immutable values. An immutable value cannot have its contents changed - any operations that seemingly alter the object actually create an altered copy in a new memory location. For instance strings are immutable in Python. In C this categorization is not needed because the relationship of variables and memory is tighter - the same variable addresses the same area of memory for the duration of its existence.
When a variable is given its initial value in code, the process is called initialization. A typical example is the initialization of a number to zero. Initialization can be done alongside with introduction: int counter = 0; or separately. If a variable has not been initialized, its content is whatever was left there by the previous owner of the memory area.
Instruction set defines what instructions the processor is capable of. These instructions form the machine language of the processor architecture.
Integers themselves are probably familiar at this point. However in C there's many kinds of integers. Integer types are distinguished by their size in bits and whether they are signed or not. As a given number of bits can represent up to (2 ^ n) different integers, the maximum value for a signed integer is (2 * (n - 1))
Python interpreter is a program that transforms Python code into machine language instructions at runtime.
The moment a variable's existence is announed for the first is called introduction. When introduced, a variable's type and name must be defined, e.g. int number;. When a variable is introduced, memory is reserved for it even though nothing is written there yet - whatever was in the memory previously is still there. For this reason it's often a good idea to initialize variables when introducing them.
Iteroitava objekti on sellainen, jonka voi antaa silmukalle läpikäytäväksi (Pythonissa for-silmukalle). Tähän joukkoon kuuluvat yleisimpinä listat, merkkijonot ja generaattorit. C:ssä ei ole silmukkaa, joka vastaisi Pythonin for-silmukan toimintaa, joten taulukoiden yms. läpikäynti tehdään indeksiä kasvattavilla silmukoilla.
Keywords are words in programming languages that have been reserved. Good text editors generally use a different formatting for keywords (e.g. bold). Usually keywords are protected and their names cannot be used for variables. Typical keywords include if and else that are used in control structures. In a way keywords are part of the programming language's grammar.
A library is typically a toolbox of functions around a single purpose. Libraries are taken to use with the include directive. If a library is not part of the C standard library, its use must also be told to the compiler.
Logical operation refers to Boole's algebra, dealing with truth values. Typical logical operations are not, and, or which are often used in conditional statements. C also uses bitwise logical operations that work in the same way but affect each bit separately.
Machine language is made of instructions understood by the processor. Machine language is often called Assembly and it is the lowest level where it's reasonable for humans to give instructions to computers. Machine language is used at the latter part of this course - students taking the introduction part do not need to learn it.
Macro is an alias that defines a certain keyword to be replaced by a piece of code. When used well, macros can create more readable code. However, often the opposite is true. Using macros is not recommended in this course, you should just be able to recognize one when you see it.
In C the main function is the starting point when the program is run. The command line arguments of the program are passed on to the main function (although they do not have to be received), and its return value type is int. At its shortest a main function can defined as int main().
When programs are run, all their data is stored in the computer's memory. The memory consists of memory slots with an address and contents. All slots are of equal size - if an instance of data is larger, a continuous area of multiple memory slots is reserved.
Method is a function that belongs to an object, often used by the object to manipulate itself. When calling a method, the object is put before the method: values.sort().
Object is common terminology in Python. Everything in Python is treated as objects - this means that everything can be referenced by a variable (e.g. you can use a variable to refer to a function). Objects are typically used in object-oriented languages. C is not one.
Optimization means improving the performance of code, typically by reducing the time it takes to run the code or its memory usage. The most important thing to understand about opimization is that it should not be done unless it's needed. Optimization should only be considered once the code is running too slowly or doesn't fit into memory. Optimization should also not be done blindly. It's important to profile the code and only optimize the parts that are most wasteful.
A parameter is a variable defined alongside with a function. Parameters receive the values of the function's arguments when it's called. This differentation between parameters and arguments is not always used, sometimes both ends of the value transfer are called arguments.
Placeholders are used in string formatting to mark a place where a value from e.g. a variable will be placed. In Python we used curly braces to mark formatting placeholders. In C the % character is used which is followed by definitions, where the type of the value is mandatory. For instance "%c" can only receive a char type variable.
Pointers in C are special variables. A pointer contains a memory address of the memory location where the actual data value is located. In a sense they work like Python variables. A variable can be defined as a pointer by postfixing its type with * when it's being introduced, e.g. int* value_ptr; creates a pointer to an integer. The contents of the memory address can be fetched by prefixing the variable name with * (e.g. *value_ptr. On the other hand, the address of a memory adress can be fetched by prefixing a variable name with &, (e.g. &value.
The C precompiler is an apparatus that goes through all the precompiler directives in the code before the program is actually compiled. These directives include statements which add the source code of the included libraries into the program, and define directives that can define constant values (aliases) and macros.
Directives are instructions that are addressed at the precompiler. They are executed and removed from the code before the actual compilation. Directives start with the # character. The most common one is include which takes a library into use. Another common one is define, which is used e.g. to create constant values.
Prototype defines a function's signature - the type of its return value, its name and all the arguments. A prototype is separate from the actual function definition. It's just a promise that the function that matches the prototype will be found in the code file. Prototypes are introduced at the beginning of the file or in a separate header file. In common cases the prototype definition is the same as the line that actually starts the function introduction.
Interactive interpreter or Python console is a program where users can write Python code lines. It's called interactive because each code line is executed after its been fully written, and the interpreter shows the return value (if any).
The format method of string in Python is a powerful way to include variable values into printable text. The string can use placeholders to indicate where the format method's arguments are placed.
Python functions can have optional parameters that have a given default value. In Python the values of arguments in a function call are transferred to function parameters through reference, which means that the values are the same even though they may have different names. Python functions can have multiple return values.
In Python the import statement is used for bringing in modules/libraries - either built-in ones, thrid party modules or other parts of the same application. In Python the names from the imported module's namespace are accessible through the module name (e.g. math.sin). In C libraries are taken to use with include, and unlike Python import it brings the library's namespace into the program's global namespace.
Python lists were discovered to be extremely effective tools in Elementary Programming. A Python list is an ordered collection of values. Its size is dynamic (i.e. can be changed during execution) and it can include any values - even mixed types. Lists can also include other lists etc.
In Python main program is the part of code that is executed when the program is started. Usually the main program is at the end of the code file and most of the time under if __name__ == "__main__": if statement. In C there is no main program as such, code execution starts with the main function instead.
In Python a variable is a reference to a value, a connection between the variable's name in code and the actual data in memory. In Python variables have no type but their values do. The validity of a value is tested case by case when code is executed. In these ways they are different from C variables, and in truth Python variables are closer to C pointers.
Pythonin for-silmukka vastaa toiminnaltaan useimmissa kielissä olevaa foreach-silmukkaa. Se käy läpi sekvenssin -esim. listan - jäsen kerrallaan, ottaen kulloinkin käsittelyssä olevan jäsenen talteen silmukkamuuttujaan. Silmukka loppuu, kun iteroitava sekvenssi päättyy.
Pääfunktio on C:ssä ohjelman aloituspiste ja se korvaa Pythonista tutun pääohjelman. Oletuksena pääfunktion nimi on main ja se määritellään yksinkertaisimmillaan int main().
Resource referes to the processing power, memory, peripheral devices etc. that are availlable in the device. It includes all the limitations within which programs can be executed and therefore defines what is possible with program code. On a desktop PC resources are - for a programmer student - almost limitless, but on embedded devices resources are much more scarce.
Return value is what a function returns when its execution ends. In C functions can only have one return value, while in Python there can be multiple. When reading code, return value can be understood as something that replaces the function call after the function has been executed.
A statement is a generic name for a single executable set of instructions - usually one line of code.
C uses static typing This means that the type of variables is defined as they are created, and values of different types cannot be assigned to them. The validity of a value is determined by its type (usually done by the compiler). Python on the other hand uses dynamic typing aka.duck typing.
In Python all text is handled as strings and it has no type for single characters. However in C there are no strings at all - there's only character arrays. A character array can be defined like a string however, e.g. char animal[7] = "donkey"; where the number is the size of the array + 1. The +1 is neede because the string must have space for the null terminator '\0' which is automatically added to the end of the "string".
Syntax is the grammar of a programming language. If a text file does not follow the syntax of code, it cannot be executed as code, or in the case of C, it cannot be compiled.
Terminal, command line interface, command line prompt etc. are different names to the text-based interface of the operating system. In Windows you can start the command line prompt by typing md to the Run... window (Win+R). Command line is used to give text-based commands to the operating system.
The data in a computer's memory is just bits, but variables have type. Type defines how the bits in memory should be interpreted. It also defines how many bits are required to store a value of the type. Types are for instance int, float and char.
Typecast is an operation where a variable is transformed to another type. In the elementary course this was primarily done with int and float functions. In C typecast is marked a bit differently: floating = (float) integer}. It's also noteworthy that the result must be stored in a variable that is the proper type. it is not possible to change the type of an existing variable.
Unsigned integer is a an integer type where all values are interpreted as positive. Since sign bit is not needed, unsigned integers can represent twice as large numbers as signed integers of the same size. An integer can be introduced as unsigned by using the unsigend keyword, e.g. unsigned int counter;.
In the elementary programming course we used the term value to refer to all kinds of values handled by programs be it variables, statement results or anything. In short, a value is data in the computer's memory that can be referenced by variables. In C the relationship between a variable and its value is tighter as variables are strictly tied to the memory area where its value is stored.
A warning is a notification that while executing or - in this course particularly - compiling it, something suspicious was encountered. The program may still work, but parts of it may exhibit incorrect behavior. In general all warnings should be fixed to make the program stable.
One way to print stuff in C is the printf function, which closely resembles Python's print function. It is given a printable string along with values that will be formatted into the string if placeholders are used. Unlike Python, C's printf doesn't automatically add a newline at the end. Therefore adding \n at the end is usually needed.
Out of loops, while is based on repetition through checking a condition - the code block inside the loop is repeated until the loop's condition is false. The condition is defined similarly to conditional statements, e.g. while (sum < 21).