Basics of Embedded C Programming: Introduction, Structure, Examples

C is a general-purpose programming language with a range of desirable features and rich applications in computing. With its origins in the assembly language, the C language includes constructs that can be efficiently mapped on to typical machine instructions, making the language useful for coding operating systems and many types of application software. 

Embedded C Language Basics

The c language, like so many other brilliant creations in computer electronics, has its origins at Bell Labs. In 1972, American computer scientists Dennis Ritchie and Ken Thompson were building the first iteration of the Unix operating system using assembly language and a 16-bit minicomputer known as the PDP-7. Thompson wished to design a programming language that could be used to write utilities for the new platform. With collaboration from Ritchie, and after several rounds of iteration and improvement, they created the C language along with a C compiler and utilities that were released with Version 2 Unix. 

C continued to develop over the years, with new language features being implemented to address changing application requirements:

  • 1978: The first C standard was published under the name K&R C, paying homage to its founders Thompson and Ritchie
  • 1989/90: The American National Standards Institute (ANSI) began to formalize a standard for the specification of the C language in 1983, releasing their finalized standard in 1989. In 1990, the ANSI standard was adopted by the International Organization for Standardization (ISO) and published in the document ISO/IEC 9899:1990. 
  • 1999: The C standard underwent further revision in the late 1990s, resulting in the publication of ISO/IEC 9899:1999. The name "C99" is commonly used to refer to this revision of the C standard. C99 introduced new data types and other features like variable-length arrays and flexible arrayed members.
  • 2011: Generic macros, anonymous structures and multi-threading were among the new features specified in the 2011 revision to the C standard.
  • 2018: The latest and most current revision to the C standard was released to provide technical corrections and additional clarification regarding aspects that were left unclear in the C11 revision. The current C programming standard and specifications can be viewed in the document ISO/IEC 9899:2018.

Embedded C is a set of language extensions for the C language that makes it more suitable for embedded applications. A technical report detailing these language extensions was released in 2004, with a revised edition of the same report released in 2016. 

As a low-level programming language, Embedded C gives developers more hands-on control over factors like memory management. This is useful for programming embedded microcontrollers that frequently face power usage and memory constraints. Embedded C also introduces the in_port and out_port functions for I/O, along with features like fixed-point arithmetic, hardware addressing, and multiple memory areas.

Embedded C Programming Preparing to write code for your next embedded systems project? It's time to open your eyes to what Embedded C has to offer.

Image courtesy of Unsplash

Introduction to Embedded C Programming

Standard C data types take on new characteristics in Embedded C to deal with the requirements of restricted, resource-limited embedded computing environments. Below, we review the characteristics of the data types available for engineers coding microcontroller systems in Embedded C.

Data Types in Embedded C

1. Function Data Types

Embedded C programs can deal with both functions and parameters. The function data type determines the type of value that can be returned by a given subroutine. Without a specified return type, any function returns a value of the integer type as its result. Embedded C also supports parameter data types that indicate values that should be passed into a specified function. When a function is declared without any parameters, or when a return value is not expected, the function can be noted as (void)

2. Integer Data Types

Embedded C supports three different data types for integers: int, short, and long. On 8-bit architectures, the default size of int values is typically set to 16 bits but Embedded C allows for int sizes to be switched between 8 and 16 bits to reduce memory consumption. The short int data type allows embedded engineers to specify and integer value that is just one or two bytes in size. Using the long int data type allocates twice the standard amount of memory as the int data type - usually 16 bits on most 8-bit platforms.

Bit Data Types

Embedded C uses two types of variables for bit-sized quantities: bit and bits. A bit value corresponds to a single independent bit while the bits variable data is used to collectively manage and address a structure of 8 bits. Programmers can assign a byte value to a bits variable to allow for the addressing of individual bits within the data.

Real Numbers

One of the most salient differences between desktop computer applications and those used to power embedded systems is their treatment of real numbers and floating-point data types. These data types include any quantity that can represent a distance along a line, including negative numbers and numbers with digits on both sides of a decimal point. Real numbers place a significant burden on the computing and memory storage capacities of embedded systems, and thus, these types of data are among the least frequently implemented. 

In addition to the conventional data types, there are also complex data types that can be useful for programming microcontrollers for embedded systems applications. These include types like pointers, arrays, enumerated types (finite sets of named values) unions, structures (ways of structuring other data), and more. 

Structure of Embedded C Program

With these data types in mind, let's take a look at the structure of a program in Embedded C. 

Documentation/Commentary

Effective coding requires the use of documentation or commentary to indicate any important details of what the code is doing. An Embedded C program typically begins with some documentation information like the name of the file, the author, the date that the code was created, and any specific details about the functioning of the code. Embedded C supports single-line comments that begin with the characters "//" or multi-line comments that begin with "/*" and end with "*/" on a subsequent line.

Preprocessor Directives 

Preprocessor directives are not normal code statements. They are lines of code that begin with the character "#" and appear in Embedded C programming before the main function. At runtime, the compiler looks for preprocessor directives within the code and resolves them completely before resolving any of the functions within the code itself. Some preprocessor directives can skip part of the main code based on certain conditions, while others may ask the preprocessor to replace the directive with information from a separate file before executing the main code or to behave differently based on the hardware resources available. Many types of preprocessor directives are available in the Embedded C language.

Global Variable Declaration

Global declarations happen before the main function of the source code. Engineers can declare global variables that may be called by the main program or any additional functions or sub-programs within the code. Engineers may also define functions here that will be accessible anywhere in the code.

Main Program

The main part of the program begins with main(). If the main function is expected to return an integer value, we would write int main(). If no return is expected, convention dictates that we should write void main(void).

  • Declaration of local variables - Unlike global variables, these ones can only be called by the function in which they are declared.
  • Initializing variables/devices - A portion of code that includes instructions for initializating variables, I/O ports, devices, function registers, and anything else needed for the program to execute
  • Program body - Includes the functions, structures, and operations needed to do something useful with our embedded system

Subprograms

An embedded C program file is not limited to a single function. Beyond the main() function, programmers can define additional functions that will execute following the main function when the code is compiled. 

Example of Embedded C Programming 

Now that we have reviewed the basic structure of programs in Embedded C, we can look at a basic example of an Embedded C program. In keeping with the structure outlined above, commentary on this code will be offered as part of the code structure itself, making use of the documentation conventions previously outlined for the Embedded C language.

/* This Embedded C program uses preprocessor directives, global variables declaration and a simple main function in a simple introductory application that turns a light on when specified conditions are satisfied */

#include <hc705c8.h>

#include <port.h>

 

/* The code imports header files that contain hardware specifications for the microcontroller system, including port addressing. The following ports are declared within the imported header file:  */

#pragma portrw PORTA @ 0x0A; 

#pragma portw DDRA @ 0x8A; 

/*In the first directive, the name PORTA refers to the data register belonging to Port A, while the suffix "rw" added to "port" indicates both read and write access. In the second directive, the suffix "w" indicates write access only to the DDRA, which is Port A's data direction register. */

#define ON 1 

#define OFF 0 

#define PUSHED 1

/* ON, OFF and PUSHED are the global variables for this program. They can be called by the main function along with any subsequent functions included by the programmer. */

void wait(registera);  

/* A wait function is needed for this program. We didn't code one but you can imagine how it would work. A wait function allows programmers to introduce a time delay between programmed events. */

void main(void){

// Beginning the main function with the expectation of no return

    DDRA = IIIIIII0;

/* We have already allowed this program to have "write" access to the DDRA port. Here, we use the assignment to set the DDRA pins to the desired configuration. In this configuration, pin 0 is set to output and pin 1 is set to input. The rest of the pins do not matter for this application. */

    while (1){ 

        if (PORTA.1 == PUSHED){ 

            wait(1);                                               

            if (PORTA.1 == PUSHED){ 

                PORTA.0 = ON; 

                wait(10); 

                PORTA.0 = OFF; 

            } 

        } 

    } 

}

/* This basic function checks the value of PORTA.1 to determine whether it is pushed (has a value of 1). If so, the program waits for 1 second and does the check again. If the second check succeeds, the program turns on an LED light by activating PORTA.0, keeps it on for 10 seconds, then shuts the light off again. */

Summary

Total Phase supports the efforts of embedded systems engineers building systems with the Embedded C programming language. In addition to our knowledge base and video resources, Total Phase provides industry-leading testing and development tools for use in embedded systems applications. Whether your device uses I2C, SPI, CAN, or USB, Total Phase offers the debugging and analysis tools you need to streamline product testing and reduce your time-to-market.

Request a Demo