This article discusses the nature and use of variables in C language in the context of embedded applications.
Many of us hear the word “variable” in math class long before we know much, if anything, about computer programming. A mathematical variable is a quantity whose value is not known or is not limited to a number. This usage is similar, though not identical, to the concept of a variable C. Two important differences are: First, in mathematics, we usually use a letter like X or Y to represent a variable, whereas in C we often use a descriptive word or phrase like temperature, Maximum valueor Number of samples. Second, there are situations where we use a variable in C to identify a quantity that is known and is never intended to be different from the original value.
Variables are convenient and intuitive for programmers. For computational hardware, on the other hand, they have no real meaning. Microprocessors store data in registers and memory locations. This fundamental difference between the people who write firmware and the machines that run the firmware is overcome by high-level languages like C, which handles various details associated with translating between text-based variables and the physical reality of a processor.
Embedded systems designers often work with 8-bit processors. In these devices, the fundamental size of the data is always one byte. Memory is organized according to bytes, the size of registers is one byte, and the CPU is designed to process 8-bit data. This is quite an awkward limitation because there are many situations where the value of a variable will exceed the maximum value of an 8-bit number.
In the end, all your carefully defined and cleverly named C variables end up as bits in memory (or registers).
The C language does not limit the size of a variable to 8 bits, even when you are working with an 8-bit processor. This means that a variable in your firmware can correspond to multiple registers or memory locations in the hardware. “Manually” managing multibyte variables (ie via assembly language) is not my idea of fun, but the compilers don’t care at all, and they do the job just fine.
The first step in using a variable is to define that variable. The essential components of a variable definition are the type and the name.
There are many types of variables; The full list, as well as the details of the hardware implementation, will vary depending on the compiler you are using. Here are some examples:
- char: a signed one-byte value
- In t– a two or four byte signed value
- long: a signed four-byte value
- float– a four-byte value that can have numbers after the decimal point; in other words, it is not limited to integers
- little bit: the value of the variable can be zero or one
This is a visual representation of how a series of bits is interpreted differently based on whether a variable is interpreted as signed (using two’s complement notation) or unsigned. See this article for more information.
The following code shows variable definitions that consist only of a basic type and a name (the most technical way to refer to the name is “identifier”):
int ADC_result; char ReceivedByte; flotador Referencia_Voltaje;
Initializing the Variable
In many cases, it is a good idea to give a variable an initial value. This makes debugging easier and is essential if the variable will be used before it is set to a known value. You can initialize a variable in the definition or elsewhere in your code, but including the initial value in the definition is a good way to keep your code organized and develop a habit of constantly initializing when necessary.
Here are examples of variable definitions that include initialization:
int ADC_result = 0; char ReceivedByte = 0x00; flotante Reference_Voltage = 2.4;
Definitions of fine tuning variables
There are several other words that can be included in a variable definition. These are used to specify the nature of the variable more precisely or to instruct the compiler on how to implement the variable on hardware.
The following keywords could be useful in your firmware projects:
- not signed: As you may have guessed, this tells the compiler to interpret the variable as an unsigned value instead of a signed value. I define most of my variables as unsigned, because I rarely need negative numbers.
- const: The const type qualifier tells the compiler that the value of a variable should not change. As I mentioned at the beginning of the article, sometimes the value of a “variable” C is not variable. If you make a mistake in your code and try to modify the value of a const variable, the compiler will throw an error.
- volatile: Sophisticated compilers don’t just take your original code and translate it directly into the assembly. They also try to make the code work more efficiently, and this process is known as “optimization.” Overall optimization is a good thing. However, from time to time, it can ruin your day, because the compiler optimizes itself based on code only and cannot take into account hardware events that interact with your code. When a variable has the volatile type qualifier, the compiler knows to be careful with optimizations related to that variable.
An interrupt can cause the value of a variable to be modified in a way the compiler doesn’t expect, and this can lead to troublesome optimization.
- types of memory, such as xdata, idataY code: These words force the compiler to place a variable in a specific part of the microprocessor’s memory. The type of code memory is particularly useful: RAM resources in a microcontroller are often much more limited than non-volatile program memory, and the type of code memory allows you to use additional program memory to store data. used on your show but never modified.
Here are some examples:
unsigned char UART_byte; // El rango de valores aceptables de la variable es de 0 a 255. const float PI = 3.14159; volatile unsigned char ADC_Register // El registro puede ser modificado por el hardware, por lo que usamos el calificador volátil para evitar optimizaciones que podrían hacer que el programa ignore los eventos generados por el hardware. código de calibración sin firmar CalibrationValue = 78;
Using your variables
There is not much to say about how to use your variables after they have been defined. Actually, with regard to the variable itself, the definition is most of the work. After that, you simply incorporate the variable’s identifier into math operations, loops, function calls, etc. A good compiler will not only handle the details of the hardware implementation but will also look for ways to optimize the code with respect to execution speed or program size.
Perhaps the most common mistake related to variable usage is an overflow. This refers to a situation where the value assigned to a variable is outside the numeric range associated with the data type of the variable. You have to think of all the possible scenarios related to a given variable and then choose the data type accordingly.
The basic variable functionality provided by the C language is intuitive and straightforward, but there are a few details that can help you make an embedded application more reliable and efficient. If you have any questions regarding C variables, feel free to mention them in the comment section below, and we will try to incorporate the relevant information in future articles.