n bits:
[-2n-1, 2n-1 - 1][0, 2n - 1][-128, 127][0, 255][-2,147,483,648, 2,147,483,647][0, 4,294,967,296]2/3...)?...)?e...)?sign x significand x baseexponent2| 0 | 00000000 | 0000000000000000000000000000000 |
| S | E | M |
1
and less than 2. Leading 1
is assumed and not encoded ⇒ actually 24 bits127An IEEE 754 converter running in a web browser:
https://www.h-schmidt.net/FloatConverter/IEEE754.html
Floating-point library:
Memory required to run some code:
Note: other configurations are possible.
The memory map is the list of the various addressable memory regions.
Example: the EFR32xG24 memory map
Available memory in the EFR32MG24 Dev Kit:
This is huge! 🙂
uint32_t val1;
int function1(void) {
uint32_t val2;
uint8_t *mem_block;
mem_block = malloc(1024);
...
free(mem_block);
}
val1: static storage
val2: automatic storage
mem_block: dynamic storage
Static storage: allocated at program startup and stays allocated
Automatic storage: allocated when entering the surrounding block, deallocated upon exit
Dynamic storage: allocated/deallocated under the control of the program
Note: try not to use dynamic storage in embedded code!
int res;
int function1(int p) {
int temp = p * 10;
return temp;
}
int function2(int p) {
int temp = function1(p);
return temp + 3;
}
res = function2(3);
...
At line 1: res is allocated from static storage
At line 13: automatic storage is allocated for the argument, 3
At line 9: temp is local to function2. Automatic storage
At line 4: temp is local to function1. Automatic storage
Automatic storage allocated to a function can be reused when the function exits.
Automatic storage is allocated from the stack.
The stack is stored in RAM.
The stack-pointer register points to the "top" of the stack.
Depending on the microcontroller, the stack may grow torwards top of the RAM or bottom of the RAM.
A microcontroller may have more than one stack pointer.
Dynamic storage is allocated from the heap.
The heap is stored in RAM.
Again: try to not use dynamic storage in embedded code (see RTOS part).
What happens when the program requires too much automatic storage?
The stack overwrites other parts of the RAM, for instance the heap, or statically allocated data.
Recursive functions may consume lot of automatic storage: try not to use them in embedded code.
At the end of the build process (compilation + linking): the amount of required static storage is known.
A default stack size is provided by the development environment. It's up to the developer to check that it is enough.
Running a program with too small a stack makes developer's life fun 🙂
The stack size can be modified in the linker script.
Additionally, the linker script tells the linker about the memory map, the heap size, etc.
⇒ It allows to generate a binary file which can be written in the flash memory at the right place.
Additionally, the linker script adds a small initialization code to the program:
main function of the programA reset may occur when:
A reset sets (most of) the microcontroller in a known state.
The microcontroller fetches the address of the code to be executed.
The code is executed.
Executed code = initialization code + application code (see a previous page).
When the program runs, several events may occur:
In what follows, we only consider events signalled by peripherals.
The microcontroller can be configured so that (some) events generate an interrupt.
Context:
Code servicing the interrupt: Interrupt Handler, Interrupt Service Routine (ISR).
Code not running in the context of an interrupt: background task.
How are events and ISRs linked?
It depends on the microcontroller. For Arm Cortex M:
| ... | ... |
| ... | USART0_RX_IRQ |
| ... | ... |
| ... | TIMER1 IRQ |
| ... | TIMER0 IRQ |
| ... | ... |
| 0x8000008 | NMI |
| 0x8000004 | Reset |
| 0x8000000 | Initial stack pointer |
Question: what happens if an interrupt is triggered while an ISR is servicing a previous interrupt?
Answer: it depends.
For most microcontrollers:
Beware: only the latest interrupt of a given type is saved.
The interrupt will not be lost. But one or more events may be lost.
⇒ Always write short ISRs
GPIO and interrupt
embedded-systems-for-ML/practice-sessions/08-Gpio-interrupt/README.md
fileTimer and interrupt
embedded-systems-for-ML/practice-sessions/09-Timer-interrupt/README.md
fileApplication and interrupts
embedded-systems-for-ML/practice-sessions/10-Application-interrupts/README.md
fileInitialization and main loop:
int main(void) {
// Initialization.
sl_system_init(); // "System" initialization.
app_init(); // Application initialization.
// Main loop.
while (1) {
sl_system_process_action(); // "System" tasks.
app_process_action(); // Application tasks.
}
}
Application - supporting code:
// Define possible states (i.e. a type for state variable).
typedef enum {
STATE_1,
...
} state_t;
static state_t current_state;
// Event flags
static volatile bool event_flag_1;
...
// Interrupt service routines.
static void on_event_1(...) {
... // Get event context, if any.
event_flag_1 = true;
}
...
Application:
void app_init(void) {
...
event_flag_1 = false;
...
current_state = STATE_1;
}
void app_process_action(void) {
switch (current_state) {
case ST_WAIT_FIRST_PRESS:
if (event_flag_1) {
event_flag_1 = false;
... // Process event, set current_state if required.
break;
}
... // Event != event_flag_1.
... // Possible error processing.
break;
... // Other states.
default:
... // Error processing.
}
}
In many applications, there are not a lot of events
Question: how can energy be saved?
Answer: enter a sleep mode between two scans for events