
This course includes our updated coding exercises so you can practice your skills as you learn.
See a demo
Explore the course structure for embedded C, covering quick C tutorial, floating point on microcontrollers without a floating point unit, interrupt interfaces, preprocessor and compiler tricks, bit manipulation, and IDEs.
Eduardo Karpin introduces fourth beyond Arduino course on C for embedded applications, covering preprocessor tricks, interrupts, bit manipulation, function call alternatives, no floating point unit strategies, and an IDE showcase.
Discover why the C programming language remains the preferred choice for embedded applications, over languages like Java, Python, or assembly, and explore embedded C for low-level microcontroller work.
Highlights motivation for learning C for embedded applications by explaining the Arduino language is plain C, a set of C/C++ functions called from your code and compiled by AVR GCC.
Explain how C and C++ differ, why Arduino uses the C++ compiler on AVR, and how the course uses C++ backward-compatible with C for embedded exercises, plus C/C++ compatibility notes.
Review the C language’s structure, control flow (if, else, switch), loops, functions, and data types, and note Kernighan and Ritchie’s recommended reading.
C is a compiled programming language that uses a compiler to produce executables, delivering faster performance than interpreted code, even when porting between architectures like Arduino and Raspberry Pi.
Compare C and Java by memory models and management: C emphasizes memory locations and manual allocation, while Java uses objects, bytecode, and a virtual machine with garbage collection.
Explain C syntax: the main function is mandatory and may return int or void. Describe argc and argv as the argument count and argument vector.
Write a hello world in C using the stdio header, showing how the preprocessor copies the header and how main calls printf to print hello world followed by a newline.
Explore variables and arithmetic in C, including integer declarations, data types, and operands, and learn how integer division truncates results while floating-point values like 5/6 produce 0 in a float.
Explore control flow in C with if-then-else statements, including the condition syntax and the difference between comparison (==) and assignment (=), and the role of the l value.
Explore the case statements in C, see how a switch evaluates a variable and executes code until a break, and use default handling for unmatched values in embedded applications.
Explore while, for, and do-while loops in C for embedded applications, and learn that a for loop is equivalent to a while loop, with initialization, condition, and increment parts.
Explore C data types for embedded applications, including char, short, int, and long, with sizes that vary on 32-bit architectures; learn floats, doubles, and treating characters as integers.
Explore how standard C data types differ by compiler using the Arduino environment, revealing 16-bit ints and 32-bit floats, with cross-platform comparisons of doubles and long doubles.
Explore how 32-bit integers are stored and allocated in ARM cortex microcontrollers, as the debugger shows a, b, and x occupying 4 bytes each and their memory addresses.
Explore how integers differ in size across architectures using CodeWarrior on a Freescale 8-bit microcontroller, revealing 16-bit ints and 32-bit floats and how type sizes depend on implementation.
Learn how the C character type uses unsigned and signed chars as 8-bit bytes to encode 0 to 255, enabling ASCII and Unicode encodings.
Identify that strings in this approach use consecutive memory bytes ending with a zero value as the end-of-string marker, enabling efficient handling when string length is unknown.
Explore character arrays in C, focusing on pointers to the first character, the null terminator, character constants in single quotes, and how string length is measured in a simple example.
Explore how C handles truth values without native booleans, where zero means false and any nonzero value means true, highlighting ANSI and canonical behavior for embedded programming.
Explore pointers, variables that store memory addresses, with p pointing to x at 102, content 11. Understand why beginners find pointers challenging as they prepare to use them everywhere.
Demonstrates C pointers using the address of and indirection operators, tracing how x and y change when ip points to x and then to z[4] within a 10-element array.
Explore how pointers can be indexed and arrays can be directed in C for embedded applications, illustrating the indirection operator and the equivalence between array indexing and pointer-based access.
Explore functions in C that return a value using a name and parameters, emphasizing modular programming and how parameters are passed by value, with pointers enabling by-reference access.
Learn a C function that squares an integer using a local automatic variable and explore printf formatting, including global versus local variables and mismatched specifiers.
Explore passing parameters by value or by reference in C using pointers; understand how functions receive copies, while passing an address uses the same address.
In this example, a function uses a by-value int and a pointer parameter to modify the original variable, illustrating address passing, indirection, and the difference between I and J lifetimes.
Discover how the C preprocessor expands macros, defines constants with #define, and conditionally includes code with #ifdef and include directives, enabling portable and efficient embedded C projects.
Defining constants with const or the preprocessor; const values live in ram and may bloat code, while define substitutes values at compile time.
Compare #define and const for embedded constants, noting #define is a preprocessor replacement with no type checking or scope. Use const to store large constants in ROM with compiler awareness.
Choose const over define in embedded C, since defines rely on the preprocessor and bypass the compiler, while const handles larger constants like floats, doubles, and longs with better safety.
Use preprocessor directives to conditionally include code at compile time, reducing memory use and code size in embedded applications. Employ if, ifdef, ifndef, else, elif, and endif with defined symbol.
Master embedded debugging under knife-in-the-woods constraints, using an lcd as a minimal signal and removing or renaming debug definitions before deployment.
Explore the #else directive in conditional compilation by examining a burglar-detection test that switches between alarm and analytics or police-notification paths at deploy time.
Explore a Wikipedia-based example of multi-platform C code, highlighting compiler-defined symbols, preprocessor directives like include and elif, and conditional portability across Unix and Win32 targets.
Demonstrate conditional inclusion of code to switch between interrupt driven and polling implementations, letting an LCD blink while another LCD reacts to a switch without delaying the blinking.
Use include guards to prevent header files from being included more than once. Define a unique symbol with #ifndef, #define, and #endif, derived from the file name in all caps.
Define large constants for values used often, and use conditional compilation to separate debugging from production code. Apply preprocessor directives to create portable, scalable embedded code across compilers and platforms.
Explore how the C compiler fits into the toolchain, converting source to executable data and applying optimizations. Learn about volatile and constant variables and casting, and how to control optimizations.
Learn to write portable C for embedded systems by using exact-width types such as int8_t, int16_t, int32_t, int64_t and their unsigned counterparts, ensuring consistent behavior across compilers.
Explore a thought experiment with global integers A and B, the main function assigning 8 to A, and a conditional print that tests whether the else branch can run.
Explain why the else part can execute in a C embedded program even if the variable a has not changed, and why the correct answer is option c.
Analyze multiprocessor systems with shared memory and the synchronization challenges of shared variables in embedded C, where compiler optimizations may alter behavior when cores race on A and B.
Explore how multi-threading splits time between functions, with the main thread running alongside other threads, and highlight the need for reliable inter-thread communication to avoid uncertainties in embedded real-time OS.
Explore hardware attached variables and memory mapped input output in embedded applications, showing how the ADC writes results into a memory mapped register and why this requires careful handling.
Explore how interrupts create parallelism by preempting code with interrupt service routines, including nested interruptions. Guard shared variables with mutexes or semaphores, or disable interrupts when needed in embedded applications.
Learn how the volatile qualifier tells the compiler that a variable may change asynchronously, preventing optimization and ensuring the code reflects real-time values, with caution not to overuse it.
Marking a with volatile disables compiler optimizations, revealing how a and related floating-point operations compile into assembly in codewarrior, including true and false branches.
Explore time consuming blocking delays in embedded applications, and how volatile variables prevent compiler optimization in a delay loop, with a Deely function that showcases potential dead variables.
Examine an example where blocking delays control timing for a repeating tune in a toy ARM Cortex-M0 application.
Learn to program the LPC1114 arm cortex-m0 32-bit microcontroller via a practical tutorial, covering hardware connections, flash programming with flash magic, and timer-driven serial applications like blinky and music.
Explore the Keil uVision 5 integrated development environment for ARM, showcasing a modified blinky demo with a mask for LCD and timer-based output using a 1<<9 parallel input/output setup.
The lecture shows a blocking, CPU-consuming delay built from nested loops and explains why declaring i and j volatile preserves the wait by preventing compiler optimization.
Explore the rest of the code that configures the LPC timer module, including the DCR and match registers, to set the duty cycle, period, and output toward the audio amplifier.
Analyze the main loop in embedded c, including initialization and a timer-driven delay that controls note timing. The example plays 'walk this way' by sequencing notes and durations.
Investigate the walk function, its blocking delays, and interrupt-driven alternatives; observe how a zero signal creates silence and how the demo uses volatile variables.
Remember the const qualifier tells the compiler not to modify a variable, while memory mapped hardware or interrupts may still change it. The volatile qualifier makes that external modification explicit.
Explore bit manipulation techniques for embedded applications by learning masking and applying logical operations to affect specific bits, and introducing C bit fields for convenient bit-level access.
Explore bit masking and bitwise operations such as and, or, xor using masks, with subnet mask examples; learn to set, clear, and flip a specific bit in a byte.
Set a specific bit in a byte by using a bitwise and/or operation with a mask. The technique places a 1 at the target position without altering the other bits.
Clear a bit by performing a bitwise and with a mask that has zero at the bit, leaving the rest unchanged. To clear multiple bits, mask those positions with zeros.
Set and clear bits via masking: or sets 0 1 1 0 1 0 0 0; and clears 1 1 1 0 1 0 1 0.
Explore a real microcontroller example from the Esthwaite family by Freescale, showing port B data register operations and bitwise mask to set or clear individual bits.
Explore masking and bitwise testing on Port A data register with six bits to react to a specific bit using a mask and an if statement in C.
Explore how to invert select bits using the XOR operation, understand a conditional inverter with a mask, and flip specific bits in a byte by XORing with a mask.
Master practical tips for embedded C bit masking: mark bits with ones, clear with zeros, use shifts to create masks, and combine with or operations via macros for maintainable code.
Showcases C example using macro mask to set bits 1 and 3, clear bits 0, 2, and 6, and invert bit 7 if bit 5 is high on port A.
Explore bit masking in Arduino via a macro that left-shifts to create masks and sets port B pin 5 as output, blinking the built-in led with 80% duty.
Explore bit fields, a compiler-supported data structure in C for accessing specific bits, enabling read and write operations, efficient memory use, and hardware control with multi-bit fields.
Demonstrate bit fields in C++, using a union to access seven one-bit day flags within a single byte, illustrating memory-efficient embedded design.
Demonstrates bit fields in a C program using a union with a days struct of one-bit fields; shows debugger view, memory layout, and decimal/binary formatting to toggle weekdays.
Explore how bit fields are implemented in Freescale libraries, using unions and structures in the SH8 header to access the ADC status and control registers and related bit groups.
Using masks to target specific bits safely and readably explains key ideas. Compare masks with C bit fields, noting the compiler and preprocessor handle details, so choose your technique.
Show how to reduce the cost of traditional functions in embedded apps by using alternatives such as lookup tables, macro functions, and inline functions, with discussion of advantages and disadvantages.
Define lookup tables as constant arrays that store function results to replace expensive calculations, enabling fast value retrieval through indexing, as used in calculators for logarithms and trigonometric functions.
Compare a traditional square function with its 256-element lookup table, showing faster table lookup but higher storage, versus a small-function approach that saves space but costs time.
Explore the caveats of lookup tables, including discrete domains, linear interpolation between points, and the substantial space needed for floating point values.
Demonstrates a benchmark between a lookup table and a regular log function for computing natural logarithms on an Arduino blinky, showing dramatic speed gains when using the table.
Decide whether to store the lookup table in ROM or RAM, balancing storage and boot time. Compute values in setup to fill RAM when time permits, avoiding RAM-initialized tables.
Define macro functions with the #define preprocessor directive and see how a square macro expands inline at call sites, unlike traditional functions which skip type checking.
Compare macro functions to traditional functions, noting that macros don't require parameters or return values, potentially run faster and avoid automatic local variables, and can improve readability by simplifying expressions.
Explore why regular functions are well-defined and handled by the compiler. Compare macro functions, which suffer precedence issues and code bloat, and learn inline functions as the safer alternative.
Explore function inlining as a compiler optimization, compare macro functions and inlining, and learn how the inline keyword and compiler decisions affect performance, including ways to request or force inlining.
Explore forcing versus suggesting inlining in embedded C, using __attribute__((always_inline)) to force inlining and adding inline after the return type to suggest it.
Leverage lookup tables to speed up code and save storage. Use macros for simple functions, and trust the compiler to decide inlining.
Explore how to handle real numbers in embedded C without a floating point unit, including software floating point, fixed point numbers, and using integers for fractions.
Identify how floating point numbers represent real values with a sign, exponent, and fraction in the binary 32 format. Compare precision and scientific notation in base 2 versus base 10.
Explains the binary 32 IEEE 754 floating point format, with a sign bit, a biased exponent (127), and an implicit one in the mantissa, covering its three encodings.
Recognize that floating point numbers are not perfect; they approximate values and can cause rounding issues. See why binary 32 cannot represent currency, as shown by the 100 million example.
Explore why certain numbers cannot be represented in binary floating point, such as 0.1 and one third, and how finite precision yields close approximations rather than exact values.
Explore the C floating point types, including float, double, and long double, covering their typical bit widths, ranges, and precision, with notes on embedded and Arduino implementations.
Define a floating point unit as a separate processor that performs floating point operations in parallel with the central processing unit, adding power, space, and cost, and supporting binary 32.
Explore software implementations of floats in embedded applications, including the runtime library that handles conversions, arithmetic, and comparisons for float32, float64, and long doubles.
Floating point numbers are binary approximations that can fail equality checks. Even when decimals seem equal, like 0.3 + 0.6 versus 0.9, binary representations differ.
Explore floating point inaccuracies in C with a Dev C++ demo, showing why exact equality fails and how epsilon-based comparison and normalized differences enable reliable float comparisons in embedded applications.
Explain how fixed point numbers use a scaling factor to represent fractional values with integers, using 123 as 1.23 and 1000 as 10.00, and discuss 8-bit and 32-bit ranges.
Explore powers-of-two scaling for fixed-point numbers in embedded C, using an 8.8 format to represent integer and fractional parts, highlighting faster fixed-point math over floating-point.
Utilize proven fixed-point libraries for embedded C applications, avoiding manual implementation of functions and types; read the library documentation and consider the authors to understand any limitations.
Explore a fixed point library for Arduino and its library manager, including template fixed point types for varying sizes.
Compare fixed point and floating point calculations in Arduino using the fixpoint library, benchmark performance, and illustrate precision trade-offs between speed and accuracy.
Use integer arithmetic for simple embedded calculations when high precision isn’t needed. Multiply by three and divide by two to simulate 1.5, but watch operator precedence, integer division, and overflow.
Explore ways to represent real numbers in C for embedded applications, comparing floats, doubles, fixed-point math, and integers, and learn about software vs hardware implementations and standard formats.
Review core C programming concepts for embedded systems, including the preprocessor, compiler, and bit manipulation. Explore cost-efficient function implementations and strategies to live without a floating point unit.
Get hands-on experience by building your own embedded projects for work, hobby, or community. Explore next steps such as real-time operating systems, FPGA embedded design, and system-on-a-chip concepts.
Master embedded applications using the C programming language as your best bet, as this farewell thanks learners and invites them to continue with Beyond Arduino and other embedded topics.
In this fourth part of the Beyond Arduino series, you'll learn many techniques to produce more efficient and professional embedded applications. It's time to leave the toy programs behind.
You'll learn how to handle memory-attached hardware registers properly, how to perform bitwise manipulation of data, how to handle interrupts on your microcontroller development platform, and many other techniques which you aren't always aware of because of the immense body of elements that conceal the details in many beginner platforms, like the Arduino, for the sake of simplicity.
You'll also learn how to deal with very basic, typically 8-bit, microcontrollers that don't have a Floating Point Unit and still write code that gets the job done in the best way possible even with the limited resources available.
After grasping this knowledge, we expect you to think differently when designing your embedded applications in the future. By adding these best practices to your bag of tricks, you'll get one step closer to making embedded applications like a professional, and hopefully you'll feel less like a beginner.