Coursera Embedded Systems

Coursera Embedded Systems Course Notes


Last Updated: May 10, 2024 by Pepe Sandoval



Want to show support?

If you find the information in this page useful and want to show your support, you can make a donation

Use PayPal

This will help me create more stuff and fix the existent content...


Coursera Embedded Systems

1. Introduction to Embedded Systems Software and Development Environments

  • An embedded system is a sytem that is built for specific purpose/application usually with limited resources (Processing, Memory, IO/Peripherals)
  • A microcontroller is a microprocessor with added functionality such as memory and peripheral hardware.
    • The CPU is the processor part of the microcontroller, the main CPU subcomponents are: registers (general & special purpose), ALU, Interrupt Controller HW, Debug interface
    • CPU interacts with memory and IO through buses
    • Memory holds data and the program the CPU executes

CPU, Buses, Memory & IO

  • HAL (Hardware Abstraction Layer)

  • A SW layer of abstraction that allows SW components above it to be independent/agnostic to the specific HW implementation

  • good interface definitions are a key for successful implementation.

  • Main Development tools for Embedded engineers

    • Simulators it's software that imitates the hardware's behavior without the actual hardware
    • Emulators it's a HW platform that imitates the operation of your intended system.
    • Compilers it's the software that creates executable code for their intented Architecture
    • Installers/Programmers it's the software-hardware combination that allows the loading of the compiled code onto a target system
    • Debuggers it's the software-hardware used to test,debug and validate the executable code

Dev Tools

  • Principles of High Quality SW for embedded systems
  • Maintainable
  • Testable
  • Portable
  • Robust
  • Efficient
  • Consistent

Embedded C

  • Embedded C: A variant of C that puts focus on certain things like

    • Efficient memory management
    • Timing centric operations
    • Direct hardware/IO control
    • Code size constraints
    • Optimized execution.
    • Optimum features in minimum space and time:
  • Types are architecture-dependent C standard only specifies minimum sizes for data types

  • C standards: Many compilers have been built to default to various versions or even only support only one standard of C programming, examples of C standards C89/C90, C99 (most common), C11 (most recent)

Common Type Size

  • SCM (Software Configuration Management)

    • Defines how we configure a software project and how does the software development process for the project works.
    • Guidelines that dictate the software processes for: version control, development process, environmental information, software build and release, software review, testing, support, defect tracking, etc.

    SCM

Build Systems

  • Build system (toolchain) refers to the the environment and the tools needed to compile a software project

Build Process

  • Most C build process consist of 5 steps

    1. Preprocesor: Evaluate preprocessor directives and perform macro substitution (Conventionally outputs *.i files)
    2. Compiler: Languages translation (for example C to Assembly) (Conventionally outputs *.o files)
    3. Assembler: Converts assembly code to object code (binary data files) (Conventionally outputs *.s files)
    • Assembly is architecture specifi language
    1. Linker: combines al object files into a single executale and resolves all references (symbols) to objects
    2. Locator/Installer: Maps all the addresses of code and data into the processor's memory space

    Build process

  • Native Compilation: compilefor the same system you intend to run the executable on. (i.e.: You have a laptop with linux compie there to run in the same laptop)

  • Cross-Compilation: compile an executable on one system intendeed to run on another

Compile and invoke GCC

  • make: Tool that controls the generation of executables
  • gcc combines Preprocesor & Compiler, as is the app for the Assembled and the Linker & Locator are combined into an app called ld, but you can do it all together with gcc
    • a Host can have multiple compiler toolchains ls -la /usr/bin/*gcc* some have very information about an specific architecture
    • Name pattern: <ARCH>-<VENDOR>-<OS>-<ABI>
      • Application Binary Interface (ABI): n interface between two binary program modules, usually one of these modules is a library or operating system d the other is a program that is being run by a user.
      • arm-none-eabi-gcc: ARCH: arm; VENDOR: NA ; OS: None ; ABI: eabi (Embedded ABI)
      • ls -la /usr/bin/arm-non-eabi* to show all tools in the cross compiler toolchain

gcc options

  • install arm-none-eabi-gcc: sudo apt install gcc-arm-none-eabi
//// Compile and link
// gcc -std=c99 -Werror -o main.out main.c
// gcc-11 -std=c99 -Werror -o main.out main.c

//// Compile
// gcc -std=c99 -Wall -v -c main.c

//// Compile arm-none-eabi-gcc
// arm-none-eabi-gcc -std=c99 -mcpu=cortex-m0plus -mthumb -Wall -S main.c -o main.s -g

//// Compile and link arm-none-eabi-gcc
// arm-none-eabi-gcc main.c -o main.out --specs=nosys.specs -std=c99 -mcpu=cortex-m0plus -mthumb -Werror

#include <stdio.h>

int main(void){
    printf("Hello World!\n"); /* Std-Library function call! */
}

Preprocessor

  • Preprocessor directives define symbols that will be replaced at preprocessing time. Examples: #define, #undef, #ifndef, #ifdef, #endif, #include, #warning, #error, #pragma
  • Preprocess command: gcc -E -o main.i main.c
  • Add macro at compile command with -D<MACRO_NAME>: gcc -DMSP_PLATFORM -o main.out main.c
  • #pragma is used to provide specific instructions for the compiler that general compile options should not do.
    • #pragma GCC poison printf sprint fprintf : Causes an error during compilation if code uses these functions
#define LENGTH (10)
#define ERROR (1)
#define UART_ERROR ERROR

#define SQUARE(x) (x*x) // Macro function: #define <MACRO-FUNCTION>(<PARAMS>) (<OPERATION>)

#define MSP_PLATFORM // No need to define a value to just use the macro as a boolean in conjunction with #ifdef directive

#ifdef MSP_PLATFORM
  msp_init();
#elif MSP430_PLATFORM
  msp430_init();
#else
  #error "Plaese specify one platfrom target"
#endif

#include "uart.h" // Include from local directory

#include <stdio.h> // Include from alibrary path or include path

Headers & Implementation files

  • Libraries are a collection of SW (can be compiled or direct source code) that we can include
  • Modules a piece of SW that encapsulates certain functionally within a library or SW project.
  • In embedded project a module usually consists of
    • An implementation file (*.c) that contains the function(s) implementation (function definition)
    • A header file (*.h) that contains the functions declaration(s), macros and type definitions
  • Standard libraries are usually precompiled, which means they are compiled and optimized for a certain architecture
  • When a library is not precompiled (you have the source code) you need to know the libraries SW dependencies and assumptions (i.e. It could use/include another library and asume you will have floating point support)
  • Libraries are usually pre-compiled for a given architecture by a specific compiler (for example we use an ARM gcc compiler you can compile libs for the ARM ISA)
    • There are different flavours of ARM ISA (i.e.: ARM Thumb Encoding, ARMv7, etc.)
    • A well designed library will support multiple architectures
  • Compiled libraries
    • Static Libraries
      • Directly linked/included in your output executable at compile time
      • Can be created with a toolo like the GNU archiver
    • Shared Libraries
      • Linked Dynamically at runtime, in other words the executable reference them and calls them but are not part of the executable.
      • They are pre-installed on the target
      • Used in systems with an OS where multiple applications use the same library, some embedded solution provide a section of memory dedicated to storing precompiled libraries
      • They can be created with specific options during compilation (for example: the shared option in gcc

libs

Linker

  • The job of the linker is to take your compiled object files and combine them into a single object file, then maps this object into specific address locations, to produce an executable program
  • The linker file is responsible for telling the linker/locator how to map our executable into the proper addresses.
    • It contains information on where the physical memory regions of the processor interface with the defined code regions.
    • This file is architecture-dependent and it needs to know the physical memory map of the system
  • Object files have symbol references to other functions/variables and a symbol table so the linker needs to merging code of multiple files, mapping symbols, and assigning addresses.
  • The symbols defined in one file and used in another need to be mapped so that the location of the symbol's address is known and assigned properly to all uses of that symbol
  • There are startup routines that run before main that are usually defined in some C standard libraries. These are automatically included in your build as a static library.
    • Use -nostdlib flag to stop including startup routines if you want to define your own initialization and exit software routines

linker

  • During linking the linker may produce a relocatable file that contains many sub segments of code blocks. This file with the defined blocks of code will need to be mapped into the architecture's memory regions by assigning a specific address to various groups of symbols.
  • Symbols are then removed from relocatable file so executable contains direct addresses, the linker file is used during this memory relocation process

relocatable file and Executable

  • The executable is broken up into many sections of code and data. These sections are then mapped into physical memory segments.
  • The total number of memory segments must be equal to or larger in size than what the total compiled code and data sections add up to,
  • The linker script can contain small checks to verify that your memory regions are not overallocated.
  • Memory region specifies access permissions such as read, write, and execute for memory blocks.
  • You can generate a map file using a linker option (like the -map flags in ld/gcc) you can use the -Xlinker and-Wl on gcc to pass arguments to the linker
  • The map file can provide information on how all theregions and memory segments were used and allocated. This map file also gives you specific addresses for the allocations.
  • Executable file formats: ELF (Executable & Linkable Format), AIF (ARM Image format), Intel Hex Record, etc

linker

make and Makefiles

  • Provides a simple mechanism to control and build consistent target executable images for your software applications.
  • It is a tool that controls generation of executables and other non-source files
  • the makefile is the file that provides directions and procedures to create executables from multiple input files
  • On the makefile we specify recipes for building particular targets/rules. These recipes usually have some dependencies and produce some type of output.
    • For example a target/rule on a makefile to build an object file can have some *.c and *.h files as dependencies
  • we use make to have a method of controlling the creation of executabled and have a portable and consistent build system, that can work over a variety of architectures (remove IDE dependencies)

make & makefiles

  • Makefiles consist of build targets also known as build rules these rules/targets have depencecies/pre-requisites (inputs that can be files or other targets)
  • Rules will check if a pre-requisite exists or try to run it, then it executes the recipe. Syntax
    • Comments start with #
    • Can include other makefiles with include
    • Uses \ for line continuatio
    • Can create variables, access to variables is done with $(VARIABLE_NAME)
      • Recursively Expanded Variables (=): expanded to whenever used
      • Simply Expanded Variables (:=): get data assigned once at definition time adn when read return the data that was assigned
      • Automatic Variables, like: $@: Target $^: All Prerequisites, $<: Prerequisites name
    • Can have multiple targets/rules and they can depend on other targets
    • Commands start with tab
    • % Operator is used to match target ocbject with associated source files
target: prereq1 prereq2 prereq3
  command1
  command2
  command3  

targets/rules

## make clean ; make all ; ./main.out
# Recursively Expanded Variables
TARGET=main.out
CC = gcc-11
CFLAGS = -Wall -Werror -g -O0 -std=c99

# Simply Expanded Variables
CWD:=$(shell pwd)
OS:=$(shell uname -s)

SRCS:=main.c
OBJS:=$(SRCS:.c=.o)


OBJS = $(SRCS:.c=.o)

%.o : %.c
    $(CC) -c $< $(CFLAGS) -o $@

.PHONY: all
all: $(TARGET)
$(TARGET): $(OBJS)
    $(CC) $(CFLAGS) -o $(TARGET) $(OBJS)

.PHONY: clean
clean:
    rm -rf main.map $(OBJS) $(TARGET)
  • Multitarget example: Introduction to Embedded Systems Software and Development Environments Week 2 Assignment

  • Check other GNU binary utilities on ls -la /usr/bin/arm-none-eabi-*

    • size display size of memory sections of compiled/executable files (ex: size -Atd main.o)
    • nm display info about symbols in an object file (T: Code, R: Read Only, D: Initialized Data, B: Uninitialized Data) (ex: nm -S --defined --size-sort -s main.o)
    • objcopy convert object/executable files from one format to another (ex: from elf32-littlearm to elf32-bigarm)
    • objdump dumbs information about an object/executable file (ex arm-none-eabi-objdump -D main.o)
    • readelf displays info about ELF files (ex: readelf main.o --all)

GNU binary utils

Memory organization

Memory hierarchy

  • In embedded systerms manufacturers do not typically list the peripheral or register memory but rather just the flash and RAM memory.
  • Examples for a KL2x family from Freescales/NXP:
    • SRAM ranges from 4KB to 32KB
    • FLASH ranges from 32KB to 256KB
  • Types of storage needed for a program: Code Memory (Flash), Data Memory (SRAM), Runtime State of program (Registers)

Memory hierarchy

Memory Architectures

  • Memory is accessed through bus and memory controllers
  • Memory Characteristics:
    • Capacity The ability to store dada and usually higher capacity means bigger physical size, increased complexity of design and power consumption which means higher cost
    • Volatility The ability to hold data without power, Volatile requires power (SRAM, DRAM, SDRAM, Cache, Registers), Non-Volatile doesnt require power to retain data (EEPROM, Dlash, Disk, etc)
    • Endurance Describes the number of write-erase cycles before failure
    • Latency Refers to the time ir takes for memory to respond to read/write requests
    • Random Access Refers to allowing access to read/write freeley to any location
      • Usually SRAM or DRAM have simple read/write process that allows you to read/write to a byte level in any location by just providing an address however
      • Other types of memories like Flash are usually organized in blocks made on many pages, read/writes can happen at a page level while erase happens at block level

Memory Characteristics

Memory Segments

  • Embedded systems usually include differnet memories (Flash, RAM, Registers, external, etc.)
  • The linkers tracks and maps memory from program into segments, using the Linker file
  • A compiler can be platform independent but needs to know details about the architecure while the Linker files MUST be platform dependent becasue it needs to know about the memory space of the platform
    • Architecture refers to the ISA of the CPU
    • Platform refers to the combination of peripherals and CPU
  • Memory Map provides a memory addres to physical device mapping (write to a memory address which in reality you are writing to physical device)
  • The general purpose and the special purpose registers of the CPU have their own address space and access method, but all other data, code, system configuration, device information and peripheral functionality are all represented through different segments but one address space
  • ABI (Application Binay Interface) gives rules on how a compiler will perform a translation fron a high leve language to architecture specific machine code, this means it defines how to use an architecture specific CPU and its registers
  • The compiler just need the addresses that it needs to read and write to access the peripherals on a platform.
    • Dependencies to map these platform specific memory to code occurs at compile time with something called a register definition file.
  • The linker file provides information to a compilation process about how an executable is to be installed into a platform.
  • Engineers usually refers to code and data sections in memory as memory segments and within these segments there are sub-segments, for data: heap, stack ; for code: text, interrupt vectors (.intvecs), bootloader (.cinit & .pinit)

Memory Map & Segments

Data Memory

  • To operate on data this is loaded into CPU registers from data memory, operations are performed on it there, and then the results are stored back into memory.

  • Data can be allocted at compile time (like global, static varibles) and at runtime (for example a local variable stored in the stack)

  • Data Segment is contaitner for the various types of data that get mapped into physical memory.

  • Data Memory Sub-segments:

    • Stack: For Temporary data like local variables
    • Heap For dynamic data storage at runtime
    • Data For non-zero initialized data like initialized global and static variables
    • BSS (Block Started by Symbol) For zero initialized and unitilized data like uninit or zero init global and static variables
  • Each sub-segment also has boundaries given either by a linker flag, or by the size of the memory you allocated.

  • The Stack is usually palced at the end and descends from high to low addresses

  • Data sizes are architecture and compiler dependent. The C standards specifies minimum size that some types must be.

Data Memory

  • Type modifiers can modify size (short, long) and sign (signed, unsigned) of data
  • Storage classes specify lifetime and scope of data
    • auto specifies variabled to be automatically allocated & deallocated on the stack. Local variabled default to this without specifying auto
    • static indicates variable should persist for the lifetime of a program but it is only available in the scope it was declared
    • extern allows for a variable actual definition/initialization to exists outside the current scope
      • Global variables are accessible by the file where they are defined but if you want to use a gloal variabled in another file you need to use extern
    • register request the compiler to allocate a variable in the CPU registers but this is not guaranteed, in general it is used to indicate this a variable that will be frequently used
The stack
  • The stack space is reserved at compile time but data is allocated there at runtime, usually grows from high to low addresses
  • It is architecture dependent (uses special instructions and registers), architectures can have different calling conventions
  • Stack Frame: refers to a set of local variables, input parameters, return data and register values that define the CPU and program current state
    • Calling convention Describes the method of how data is passed in from a routine, in other words how stack frame are stored and restored from the stack
    • Execution flow change (enter function): copy parameters to stack, save state, jump into sub-routine code/instructions
    • Remove sub-routine (return from function): return data, restore state
  • The ABI (Application Binay Interface) defines how the registers are used to manage data. For example in ARM
    • Registers r0-r3 are sued to pass arguments into a function which means having functions that take no more than 4 arguments is recommended
    • Registers r4-r11 are used to store local variables which means having functions that have no more than 8 local variables is recommended
    • Registers r14==LR Link register that holds return address and r13==SP stack pointer that points to the last location used of the stack memory
    • register r0 is used to return the resulting data (data returned by return)
  • Stack is a LIFO: Last-In Firtst-Out, push copies dat from registers to stack and pop removes data from stack and puts it on registers

The stack

The Heap
  • Needs specific functions support (malloc calloc, realloc & free) to manage this region of memory
    • malloc & calloc allocates continugas bytes in the Heap, calloc reservers space and initialize it to zeros while malloc just reserves space
    • realloc reallocates region to new size and frees old space, requires original heap pointer & new size

Code Memory

  • Instructions are fetched from code memory to be decoded and executed by the CPU.
  • Code segment usually placed in flash that is runtime read only but can be put into write mode during runtime with extra credentiasl/interactions. Use case of this are bootloaders or executable update
  • The code segment contains all the instructions of our executable and also some data -
  • The linker file also specifies what physical memory the code segments map to.
  • Code Memory Sub-segments:
    • .intvecs (in ARM typically stored address zero of code memory):
      • A table with addresses of functions, those functions are interrupt handlers.
      • You write a function interrupt handler, then map that handler by writing the function's address on the table
      • When CPU needs to handle an interrupt, first looks on the table for the address it need to jump to
    • .text: Stores all of your actual code instructions (main and all your functions), functions from the libraries you include (like the C standard library) are also added here
    • .const Stores const variables
    • .cinit & .pinit
      • Used to store that needed for initialization of variables.
      • Contain initialized variables that are loaded into data memory from code memory each time the program is
  • .bootloader For when you want to reserve a small segment of your code memory to act as your installer.

The bootloader would be a small program that at reboot looks for a signal on one of its communication interfaces signalling a program install. If it sees this, it will do whats necessary to overwrites the current flash memory with the new program. If it does not see anything, it will just call the currently loaded program and begin program execution.

  • Compilers will automatically add some C standard functions when your code is compiled, you can avoid this if you write you own baremetal interface and using the --nostdlib compile option

Code Memory

2. Embedded Software and Hardware Architecture

  • Embedded Application Binary Interface (EABI) This is what defines how your hardware wll implement your C program code on your platform.

    • Provides details on how a binary must be compiled and interfaced with platform (refers to the combination of peripherals and CPU) components
    • Specifes details of how executable must run on certain architecture
    • Defines register used and word size (operand size of the ISA=Instruction Set Architecture)
    • Specified how program code and data are physically sized and placed into memory.
    • Define addressing modes which describe how your program operates on memory (register addressing, memory direct, indirect addressing with or wihtout offset, etc)
    • Defines calling convention: what needs to be saved or restored by the calling routine, and what needs to be done by the routine being called.
    • Specifies which libraries or helper software functions are supported by an architecture. For example without HW floating point support this can be implemented with software
    • Specify how executables need to interact with the standard library
  • Instructions are the fundamental unit of operation on the CPU, they are fetch decoded and executed by the CPU

  • Word fundamental operand size fo r each operation

  • General purpose registers in the CPU will be the size of the Word

  • The C-Standard only guarantees a minimum size that each of standard types might be.

Instructions & Word Size

  • ARM Cortex has an Address space of 2^32 byte addressable memory addresses (4GB)

Interacting with memory

  • Load-Store Architecture
    • Data is loaded from memory and results are stored back in memory, while all processing occurs in the CPU core
    • It is Read-Modify-Write process
      • Read: loads data into CPU
      • Modify: Data is operated on
      • Write: Resul is stored back
    • There are specific instructions to load and store
    • We cannot operata directly on memory, operations occurs inside CPU

Load/Store

  • Bit-Banded regions: sections of the addres space that allowss to perform bit-level loads and stores, this allos atomic operations on bits

  • AMBA (Advanced Microcontroller Bus Architecture) Specification of the Buses in the microcontroller. On a Microconteoller there are many buses used by the CPU:

    • AHB = AMBA High-Performance Bus type of bus defiend by ARM for high performance
    • Internal PPB = Private Peripheral Bus
    • System Bus (an AHB-Lite on ARM Cortex M4): Usually refers to the bus that connects the CPU to the SRAM
    • Flash Bus that has I-Code Bus to fetch instructions (from code memory) and D-Coide Bus to fetch data (from code memory)
    • APB = Advanced Peripheral Bus Low bandwith Bus usually for peripheral devices (UART, SPI, I2C, Timers, ADC, etc.)
      • Connected to CPU via a Bride, which means CPU writes to memory usuaing system Bus then this is transfered to the APD via bridge

Buses

  • ARM ISA has byte, half-word and word length load & stores
  • Load and store ocurrs only at aligned addresses in memory and padding is used by some architectures to always have aligned memory access
  • Any address in memory is byte addressable so byte access are always aligned
  • Half words can only be stored when aligned to an even address (addresses that end with: 0x0, 0x2, 0x4, 0x6, 0x8, 0xA, 0xC, 0xE)
  • Words can only be stored when aligned with 4 byte boundarys (addresses that end with: 0x0, 0x4, 0x8, 0xC)
  • Optimization for speed is achieved if data is aligned.
  • Optimization for storage is achieved if data is packed.

Mem Alignment

Endianness

  • Defines how data is ordered in memory, in other words how bytes are ordered in memory
  • Big endian Most Signigficant byte is stored first (on the lowest address, 0xABCD1234 -> 0x0:0xAB 0x1:0xCD...)
  • Little endian Least Signigficant byte is stored first (on the lowest address, 0xABCD1234 -> 0x0:0x34 0x1:0x12...)
  • ARM by default works as little endian
  • if two systems with different endianness are communicating you may need to perform reordering to maintain same meaning of data

Endianness

  • __attribute__ give details to the compiler on how to compile code for: variblaes, structs and functions.
    • int8_t var __attribute__((aligned(4));
    • __attribute__((packed))
    • __attribute__((always_inline)) inline int32_t add(int32_t, x, int32_t y) { return (x+y); }

Registers

  • Special CPU core registers are not in the standard memory map and require special assembly instructions to read or write data to them. (MRS & MSR)

  • PSR Program Status Register: tracks current program state

    • APSR Application Program Status Register: contains condition flags from previous instruction execution, used to track conditional and arithmetic operations
    • IPSR Interrupt Program Status Register: contains exception type number of current ISR
    • EPSR
  • EMR Exception Mask Registers

    • PRIMASK: Enables disables all Exceptions
    • FAULTMASK: Enables disables all Exceptions except for the NMI
    • BASEPRI Defines minimum priority for exception processing
  • Control Register: allows to select privileged/non-privileged state and also switch which stack pointer is being used for normal program flow and which is used for interrupts

  • There are numerous peripherals both internal, external to the CPU core with registers for configuration and data of the pheripherals

Register Definition Files

  • Specifies addresses of the peripheral registers
  • Directly Dereference Memory: #define REGISTER_NAME (*((volatile uint16_t*) 0x40000000)); REGISTER_NAME = 0x0202
  • Structure Overlay: Define a struct to match peripheral region registers
    • This requires an exact replica of the physical memory defined in your structure definition, so you need to leave space for reserved bytes.
    • Uses the structure pointer dereference operator to access registers

Structure Overlay

Want to show support?

If you find the information in this page useful and want to show your support, you can make a donation

Use PayPal

This will help me create more stuff and fix the existent content...