Recent Posts
- jQuery attrAugust 12, 2021
- jQuery mouseenterAugust 9, 2021
- jQuery ToggleclassAugust 6, 2021
- jQuery attr
By the standards of modern technology, C is a rather old language. The initial development took place in the early 70s, followed by revisions in the late 70s and standardization in the 80s. Nevertheless, in my opinion, it has lost none of its vigours. It’s still a great language for embedded applications, and in my experience, it is a suitable programming environment for everything from simple microcontroller-based devices to sophisticated digital signal processing.
I do not doubt that there are at least a few electrical engineers who do not know how to write a program in C and never will need to write a program in C. If you’re the sort of person who prefers hardware to software, you might consider these individuals “the lucky ones.”
Whether we like it or not, though, programming is an increasingly important part of electrical engineering. I have found much satisfaction in being able to not only design circuit boards but also write the firmware for those boards. These two aspects of system development are closely related, and I suspect that the result is often superior when the same person carries outboard design and firmware development.
In theory, I am a proponent of assembly language. In reality, I have reached a point in my life at which assembly language is a threat to both my financial security and my sanity. Writing firmware in assembly is also slow and error-prone, and maintaining an adequate level of organization in long, complex programs is hopelessly difficult.
However, I will certainly insist that you cannot understand high-level languages if you don’t understand assembly. Suppose you’ve never had the opportunity to gain some solid experience with assembly language. In that case, you should at least familiarize yourself with some of the basic concepts before you dive into C.
Only machine language. Ones and zeros. All of the “programmer-friendly” aspects of the C language must eventually be translated into the low-level reality of the processor’s digital hardware—i.e., binary arithmetic, logical operations, data transfer, registers, and memory locations.
It certainly is possible to successfully write a program in C while knowing nothing about the actual hardware. Still, in the context of embedded systems development, it’s helpful and sometimes necessary to understand both your hardware and how your C code interacts with that hardware.
C programs range from those that are quite simple to those that are very complex. In the embedded world, many programs will tend toward the simple side of the spectrum, and the basic programming elements described below provide a good foundation for further study of C-language firmware development.
An embedded C program will begin with at least one #include statement. These statements are used to introduce the contents of a separate file into your source file. This is a handy way to keep your code organized, and it also allows you to use library functionality, hardware-configuration routines, and register definitions provided by the manufacturer.
The code excerpt below shows the include statements that I used in one of my microcontroller projects. Note that the “Project_DefsVarsFuncs.h” file is a custom header file created by the programmer (i.e., me). I was also using it as a convenient way to incorporate preprocessor definitions, variables, and function prototypes into multiple source files.
//—————————————————————————–
/Includes
//—————————————————————————–
#include // SFR declarations
#include “Project_DefsVarsFuncs.h”
#include “InitDevice.h”
#include “cslib_config.h”
#include “lib.h”
You can use a #define statement to create a string that will be replaced by a number. Preprocessor definitions are not necessary, but in some situations, they are extremely helpful because they allow you to easily modify a value that appears in various portions of your program.
For example, let’s say that you’re using the microcontroller’s ADC and that your code uses the ADC’s sample rate in several separate calculations. A preprocessor definition allows you to use an intuitive string (such as SAMPLE_RATE) instead of the number itself in the calculation code. If you’re experimenting with different sample rates, you only need to change the one numerical value in the preprocessor definition.
You can change 100000 to any other number, and this new number will be used to replace all instances of the string SAMPLE_RATE.
Preprocessor definitions are also a great way to make code more readable. The following is a list of handy #define statements that I incorporate into all of my firmware projects.
#define BIT7 0x80
define BIT6 0x40
#define BIT5 0x20
define BIT4 0x10
#define BIT3 0x08
define BIT2 0x04
#define BIT1 0x02
define BIT0 0x01
#define HIGH 1
define LOW 0
#define TRUE 1
define FALSE 0
#define SET 1
define CLEARED 0
#define LOWBYTE(v) ((unsigned char) (v))
define HIGHBYTE(v) ((unsigned char) (((unsigned int) (v)) >> 8))
It’s also important to understand that preprocessor definition have no direct relationship to hardware. You’re just telling the preprocessor to replace one string of characters with another series of characters before the program is compiled.
Processors store data in registers and memory locations. There is no such thing as a variable as far as the hardware is concerned. For the programmer, though, writing code is much easier when we can use intuitively named variables instead of memory addresses or registration numbers.
Compilers can manage the low-level details associated with variables without much input from the programmer. Still, if you want to optimize your use of variables, you’ll need to know something about the device’s memory configuration and how it handles data of different bit widths.
The following code excerpt gives an example of a variable definition. This was written for the Keil Cx51 compiler, which reserves one byte of memory for an “unsigned char” definition, two bytes for an “unsigned int” definition, and four bytes for an “unsigned long” purpose.
unsigned long Accumulated_Capacitance_Sensor1;
long Accumulated_Capacitance_Sensor2 unsigned;
unsigned int Sensor1_Unpressed;
int Sensor2_Unpressed unsigned;
unsigned int Sensor1_Measurement;
unsigned int Sensor2_Measurement;
int AngularPosition unsigned;
unsigned int TouchDuration;
unsigned char CurrentDigit;
int CharacterEntry unsigned;
unsigned char DisplayDivider;
The core of computational functionality consists of moving data, performing mathematical computations and logical operations with data, and making programmatic decisions based on the value of stored or generated data.
Mathematical operations and bit manipulation are accomplished using operators. C has quite a few operators: equals (=), addition (+), subtraction (-), multiplication (*), division (/), bitwise AND (&), bitwise OR (|), and so forth. The “inputs” to an operator statement are variables or constants, and the result is stored in a variable.
Conditional statements allow you to perform or not perform an action based on whether a given condition is true or false. These statements use the words “if” and “else”; for example:
if(Sensor1 < Sensor2 && Sensor1 < Sensor3)
return SENSOR_1;
else if(Sensor2 < Sensor1 && Sensor2 < Sensor3)
return SENSOR_2;
else if(Sensor3 < Sensor2 && Sensor3 < Sensor1)
return SENSOR_3;
else
return 0;
For loops and while loops provide a convenient means of repeatedly executing a block of code. These types of tasks arise very frequently in embedded applications. For coils are more oriented toward situations in which a block of code must be executed a specific number of times, and while loops are handy when the processor should continue repeating the same block of code until a condition changes from true to false. Here are examples of both types.
for (n = 0; n < 16; n++)
{
Accumulated_Capacitance_Sensor1 += Measure_Capacitance(SENSOR_1);
Delay_us(50);
Accumulated_Capacitance_Sensor2 += Measure_Capacitance(SENSOR_2);
Delay_us(50);
}
while(CONVERSION_DONE == FALSE);
{
LED_STATE = !LED_STATE;
Delay_ms(100);
}
Good C code is vastly superior to assembly code in terms of organization and readability, and this is due in large part to the use of functions.
Functions are blocks of code that can be easily incorporated into other portions of code. Causing the processor to execute the instructions contained in the process is referred to as “calling” the function. A function can accept one or multiple inputs, and it can provide one output, called a return value.
The use of functions does involve some overhead, so we have to be careful to not burden the processor with an excessive number of function calls, but in general, the benefits of functions far outweigh the costs.
Here is an example of a function that has three numerical inputs and uses these inputs to generate a true-or-false return value.
bit Is_In_Range(int input, int LowerBound, int UpperBound)
{
if(input >= LowerBound && input <= UpperBound)
return TRUE;
else
return FALSE;
}
A thorough discussion of the C language could go on almost indefinitely, and this article has only scratched the surface.
If you have any C-related topics that you’d like to learn more about, feel free to let us know in the comments section below.
Hope you like our this post also do read our other post.
Read more about our courses.
MCP is the right place for best computer Courses institute and advance Courses in Mohali and Chandigarh.The Complete Programming Academy can change your life – providing you with the knowledge, skills, and performance in a second language which helps you to excel in your job.You can also Contact us for 6 month industrial training institute in Mohali.