Google News
logo
Embedded C Interview Questions
Embedded C is a set of language extensions for the C programming language specified by the C Standards Committee. C programming language is used to develop desktop based applications. While, Embedded C is used to develop micro-controller based applications such as device drivers (memory device driver, camera device driver, WIFI device drive etc.)
 
Most of the low-level applications which interact with the hardware are written in C programming language, it is also used to develop complete operating systems to electronic devices – which are generally known as firmware’s.
 
Embedded C programming typically requires nonstandard extensions to the C language to support enhanced microprocessor features such as fixed-point arithmetic, multiple distinct memory banks, and basic I/O operations.
C Language Embedded C Language
C is a general-purpose programming language used to design any desktop-based applications. Embedded C is nothing but an extension C programming language, and it is used to develop micro-controller based applications.
C is a type of high-level programming language. Embedded C is an extension of the C programming language.
C programming language is the hardware-independent language. Embedded C is a completely hardware-dependent language.
C is a simple language, and it is easy to read and modify. Embedded C is comparatively tough, and it is not easy to read and modify the Embedded C language.
The compilers of the C programming language are OS-dependent. The compilers of Embedded C are OS independent.
In the C programming language, the standard compilers are used to compile and execute the program. In Embedded C language, a specific compiler that can generate particular hardware/micro-controller based output is used to compile the code.
Some popular compilers used to execute a C language program are:
GCC (GNU Compiler collection)
Borland Turbo C
Intel C++
Some popular compilers used to execute an Embedded C language program are:
Keil compiler
BiPOM ELECTRONIC
Green Hill software
C programming language has a free format of program coding. In Embedded C language, formatting depends upon the type of microprocessor used in the application.
Bug fixing is easy in a C language program. Bug fixing is complicated in an Embedded C language program.
C language also supports other various programming languages during application. Embedded C language supports only the required processor of the application and not the programming languages.
C programming language must require an operating system. Embedded C may or may not require an operating system.
In the C programming language, we can give input to the program while it is running. In Embedded C language, we can give only the pre-defined inputs to the running program.
The C programming language supports normal optimization. Embedded C supports the high level of optimization.
C programming language generally uses the resources of a desktop PC like memory, OS, etc. Embedded C language has to use with the limited resources, such as RAM, ROM, I/Os on an embedded processor.
Some examples of the C Program applications:
Logical programs
System software programs etc.
Some examples of the Embedded C Program applications:
DVD
TV
Digital camera etc.
Essential components of embedded system includes
 
* Hardware
* Processor
* Memory
* Timers
* I/O circuits
* System application specific circuits
* Software
* It ensures the availability of System Memory
* It checks the Processor Speed availability
* The need to limit power lost when running the system continuously
* Real Time Operating System
* It runs a process as per scheduling and do the switching from one process to another
An embedded system is a microprocessor-based computer hardware system and software designed to perform a specific function. An embedded system is either an independent system or acts as a part of a large system.
Embedded C is the extension of the C programming language. Let's see the key advantages of Embedded C:
 
Key advantages of Embedded C :
 
* The coding speed of Embedded C is high, and it is simple and easy to understand.
* It doesn't require any hardware changes such as extra memory or space for storage as it performs the same task all the time.
* It is dedicated to its specific task and performs only one task at one time.
* It is mainly used in modern automatic applications. Embedded applications are very suitable for industrial purposes.
Disadvantages of Embedded C :
 
* Embedded C performs only one task at a time, so it is not preferred when we have to perform multiple tasks simultaneously.
* Embedded C only supports the hardware system. So, if you have to change the program, then you must have to change the hardware.
* Embedded C also has some scalability issues, so; it cannot be easily scaled up as scope change or demand.
* It has some limitations, such as limited memory or computer compatibility.
Segmentation fault is a runtime error, which may occur due to some causes (listed below) when the program is running properly. There are some of the cases (causes), when segmentation fault error may occur,
 
* Usages of the dereferenced pointer (i.e. a pointer which may not have a valid address/memory location to point).
* If you are trying to access a memory area which is read-only. In that case, the program may return segmentation fault error.
* It may also occur when you try to free a memory (using a pointer), which is already freed.
* Segmentation fault is the reason to generate stack overflow error in C.
The purposes to use a static variable are :
 
* A static variable does not redeclare that means if it is declared in a function it will not redeclare on each function call, therefore, static is able to maintain the value.

* Its scope is local but it is live until the end of the program.

* Generally, it is used to count something, for example, there is function openBakAccount() which calls every time when a new account opens in the bank. Then, to count the total number of the opened account, we can declare a static variable in the function and can increase it on each function call.
* DRAM(Dynamic RAM)
* SRAM(Static RAM)
* Mask Read-only Memory (ROM)
* PROM(Programmable Read-Only Memory)
* UV EPROM(UV Erasable Programmable Read-Only Memory)
* EEPROM (Electrically – Erasable PROM)
10 .
ISR Stands for "Interrupt Service Routine." An ISR (also called an interrupt handler) is a software process invoked by an interrupt request from a hardware device. It handles the request and sends it to the CPU, interrupting the active process. When the ISR is complete, the process is resumed.
 
A basic example of an ISR is a routine that handles keyboard events, such as pressing or releasing a key. Each time a key is pressed, the ISR processes the input. For example, if you press and hold the right arrow key in a text file, the ISR will signal to the CPU that the right arrow key is depressed. The CPU sends this information to the active word processor or text editing program, which will move the cursor to the right. When you let go of the key, the ISR handles the "key up" event. This interrupts the previous "key down" state, which signals to the program to stop moving the cursor.
 
Many types of hardware devices, including internal components and external peripherals can sent interrupts to the CPU. Examples include keyboards, mice, sound cards, and hard drives. A device driver enables communication between each of these devices and the CPU.
Void pointers are those pointers that point to a variable of any type. It is a generic pointer as it is not dependent on any of the inbuilt or user-defined data types while referencing. During dereferencing of the pointer, we require the correct data type to which the data needs to be dereferenced.
 
For Example :
int num1 = 20;    //variable of int datatype 
void *ptr;        //Void Pointer
*ptr = &num1;    //Point the pointer to int data
print("%d",(*(int*)ptr));    //Dereferencing requires specific data type 

char c = 'a';
*ptr = &c;    //Same void pointer can be used to point to data of different type -> reusability
print("%c",(*(char*)ptr));
 
Void pointers are used mainly because of their nature of re-usability. It is reusable because any type of data can be stored.
This error may occur if the program tries to access the memory beyond its available maximum limit. We can also say that if a pointer exceeds the stack limitations (boundaries).
 
When this error occurs program terminates and does not execute further instructions. Therefore, we must be careful while using the pointer and limit boundaries.
volatile is used to prevent the compiler to optimize any variable.
 
When any variable is used frequently, the compiler optimizes it and keeps the variables in his memory (there are some specific memory blocks (registers), from there variable is accessibility is fast) to serve its value faster to the program.
 
Therefore, a volatile is used to prevent the compiler for any type of optimization on the variable. Volatile variables are used with those variables which are used to communicate with the computer hardware, signals, handlers etc.
 
Consider the given code below :
//global declaration 
unsigned char isDeviceAttached = 0;
//other code segment 

int main()
{
	//system configuration
	//other code segment
	//any loop/condition which is using the variable 
	which(isDeviceAttached)
	{
		//code to handle particular device 
	}
	//other code

	return 0;
}
In the above code, variable isDeviceAtteched is declared globally and is using frequently in the program. The compiler may optimize it and keep its ‘0’ in the memory to serve it faster.
 
But, the value of isDeviceAtteched may change when we attached the device to signal/handler/interrupt function.
 
If we will not use volatile above condition loop’s condition will be false always. And if we make it volatile variable, the actual value will be served to the program.
 
volatile variable’s declaration :
volatile unsigned char isDeviceAttaced;
volatile variable’s declaration, definition (when it is used it two files)
 
Definition :
volatile unsigned char isDeviceAtteched =0;
Declaration :
extern volatile unsigned char isDeviceAtteched;
No. It is not possible to pass any parameter and return a value from the ISR. The ISR returns nothing and does not allow passing any parameter. An ISR is called when a hardware or software event occurs, and the code does not call it. That's why no parameters are passed into an ISR. As the code does not call ISR, there is no calling code to read the returned values of the ISR. That's why an ISR does not return any value.
const volatile
The keyword “const” is enforced by the compiler and tells it that no changes can be made to the value of that object/variable during program execution. The keyword “volatile” tells the compiler to not perform any optimization on the variables and not to assume anything about the variables against which it is declared.
Example: const int x=20;, here if the program attempts to modify the value of x, then there would be a compiler error as there is const keyword assigned which makes the variable x non-modifiable. Example: volatile int x;, here the compiler is told to not assume anything regarding the variable x and avoid performing optimizations on it. Every time the compiler encounters the variable, fetch it from the memory it is assigned to.
Interrupt Latency is the number of clock cycles the processor takes to respond to an interrupt request. This clock cycle number is count between the interrupt request's assertions and the interrupt handler's first instruction.
 
Interrupt Latency on the Cortex-M processor family :
 
Cortex-M processors have very low interrupt latency. The following table shows the Interrupt latency of Cortex-M processors with zero wait state memory systems.

Processors Cycles with zero wait state memory
Cortex-M0 16
Cortex-M0+ 15
Cortex-M3 12
Cortex-M4 12
Cortex-M7 12
In Embedded C, we can measure the interrupt latency with the help of the oscilloscope. Follow the steps given below:
 
* Take two GPIOs first. Configure one GPIO to generate the interrupt and the second for the toggling (you can attach an LED also).
* Use the oscilloscope or analyzer to monitor the PIN, which is already configured to generate the interrupt.
* Now, monitor the second pin, which is toggled at the beginning of the interrupt service routine by using the oscilloscope or analyzer.
* When you generate the interrupt, the signal of both GPIOs will change.
* Now, you can easily read the instrument's interval between the two signals (interrupt latency).
There are several ways to reduce the interrupt latency in Embedded C. The interrupt latency depends on many factors. Following is a list of some factors:
 
* Platform and interrupt controller
* CPU clock speed
* Timer frequency
* Cache configuration
* Application program etc.

So, we can easily reduce the interrupt latency by using the proper selection of platform and processor. We can also reduce the interrupt latency by making the ISR shorter and avoid to calling a function within the ISR.
The Concatenation operator is indicated by the usage of ##. It is used in macros to perform concatenation of the arguments in the macro. We need to keep note that only the arguments are concatenated, not the values of those arguments.
 
For example, if we have the following piece of code :
#define CUSTOM_MACRO(x, y) x##y

main(){

  int xValue = 20;
  printf(“%d”, CUSTOM_MACRO(x, Value));    //Prints 20

}
We can think of it like this if arguments x and y are passed, then the macro just returns xy -> The concatenation of x and y.
extern keyboard can be to declare a variable which allows accessing the variable in another file.
 
Let’s understand how it will possible?
 
file1.c
/*include header file "file2.h"
where variable is declare*/ 
#include "file2.h"

...
/*Access the variable, where it is 
Required*/
if(flgUSB){

    ...;
}

file2.c
/*define variable in global scope*/
unsigned char flgUSB=0;
/*while defining a variable, 
you can assign a default value to it*/

file2.h
/*declare variable in global scope*/
extern unsigned char flgUSB;
/*do not use default value here, it will generate an error*/
Explanation :
 
If the variable is defined in "file2.c" and you want to access it in "file1.c", then you have to create a header file like "file2.h".
 
Declare the variable using extern in the header file and then include it file in "file1.c" or wherever you want to access the variable.
Constant character pointer (const char*) prevents the unnecessary modifications with the pointer address in the string.
 
Example :
///declaration 
const char *str;

//we can reassign the pointer
//string will be declared somewhere in the memory and 
//its address will be assigned to the "str" 
str = "Hello world";

//but, we cannot modify the data 
//that is pointing by the pointer

*str = 'P';//not allowed
Category Macro Function Inline Function
Compile-time expansion Macro functions are expanded by the preprocessor at the compile time. Inline functions are expanded by the compiler.
Argument Evaluation Expressions passed to the Macro functions might get evaluated more than once. Expressions passed to Inline functions get evaluated once.
Parameter Checking Macro functions do not follow strict parameter data type checking. Inline functions follow strict data type checking of the parameters.
Ease of debugging Macro functions are hard to debug because it is replaced by the pre-processor as a textual representation which is not visible in the source code. Easier to debug inline functions which is why it is recommended to be used over macro functions.
Example #define SQUARENUM(A) A * A -> The macro functions are expanded at compile time. Hence, if we pass, the output will be evaluated to 3+2*3+2 which gets evaluated to 11. This might not be as per our expectations. inline squareNum(int A){return A * A;} -> If we have printf(squareNum(3+2));, the arguments to the function are evaluated first to 5 and passed to the function, which returns a square of 5 = 25.
Following is a list of some interesting facts about static variables in C :
 
* There are two types of static variables, static int variable and static auto variable. A static int variable remains in memory while the program is running and a normal or auto variable is destroyed when a function call declared is over. For example, we can use a static int variable to count the number of times a function is called, but we cannot use an auto variable for this purpose.

* Static variables are allocated memory in the data segment, not the stack segment.

* The default value for static variables is 0. Like global variables, they are initialized as 0 if not initialized explicitly.

* In C language, we can initialize the static variables using only constant literals.

* Static global variables and functions are also possible in C++. They are mainly used to limit the scope of a variable or function to a file.

* We should not declare static variables inside the structure because the C compiler requires the entire structure elements to be placed together.
An infinite loop is the main component of an embedded application, which is used to run an application all time, an infinite loop can be coded by using while (1) and for(;;)
 
Syntax for an infinite loop by using while(1)
while(1)
    {
	    ...;
	    ...;
    }
 
Syntax for an infinite loop by using for(;;)
for(;;)
    {
	    ...;
	    ...;
    }
Void main( void)

      {

       //prepare run function x

        X_Init();

        While(1)  //  ‘for ever’ (super loop)

         {

           X(); // Run function x()

          }

       }
There are many differences between the RISC and CISC.
RISC CISC
RISC stands for Reduced Instruction Set Computer. CISC stands for Complex Instruction Set Computer.
RISC does not consist of a memory unit. CISC consists of a memory unit.
RISC is a relatively faster processor than CISC in terms of calculations. CISC is a comparatively slower processor than RISC in terms of calculations.
RISC is used to ensure the simple decoding of operations. CISC doesn't ensure simple decoding of operations.
The execution time is RISC is low. The execution time is CISC is high.
The Pre-decrement operator (--operand) is used for decrementing the value of the variable by 1 before assigning the variable value.
#include < stdio.h >  
int main(){  
    int x = 100, y;  
  
    y = --x;   //pre-decrememt operators  -- first decrements the value and then it is assigned 

    printf("y = %d\n", y);  // Prints 99
    printf("x = %d\n", x);  // Prints 99
    return 0;  
}  
The Post-decrement operator (operand--) is used for decrementing the value of a variable by 1 after assigning the variable value.
#include < stdio.h >  
int main(){  
    int x = 100, y;  
  
    y = x--;   //post-decrememt operators  -- first assigns the value and then it is decremented 

    printf("y = %d\n", y);  // Prints 100
    printf("x = %d\n", x);  // Prints 99
    return 0;  
}
A function pointer is a pointer that points to a function instead of a variable. That's why a function pointer is completely different from the class of other pointers. A function pointer stores the address of a particular function so that the concerned program can avail of it through function invoking.
Yes, a variable can be both constant and volatile in C. we can use constant and volatile both variables together. The volatile and const variable together is used at the time of accessing the GPIO registers. In this case, its value is changed by the 'external factors' if a switch or any output device is attached with GPIO. In this situation, the volatile variable is important because it ensures that the compiler always read the value from the GPIO address and avoids making any assumption.
A dangling pointer is a pointer that points to a memory location that has been already free-ed by the application and is no longer in use. Sometimes, the programmers fail to initialize the pointer with a valid address; these types of initialized pointers are known as dangling pointers. Dangling pointers occur at the time of the object's destruction when the object is deleted or de-allocated from memory without modifying the pointer's value. If you try to dereference a dangling pointer, it will potentially lead to a runtime error.
A null pointer is a pointer that does not point to any valid memory location. It is defined to ensure that the pointer should not be used to modify anything as it is invalid. If no address is assigned to the pointer, it is set to NULL.
 
Syntax : 
data_type *pointer_name = NULL;
One of the uses of the null pointer is that once the memory allocated to a pointer is freed up, we will be using NULL to assign to the pointer so that it does not point to any garbage locations.
1. const int x;
2. int const x;
3. const  int  *x;
4. int * const x;
5. int  const * x const;
 
* The first two declaration points 1 and 2 mean the same. It means that the variable x is a read-only constant integer.

* The third declaration represents that the variable a is a pointer to a constant integer. The integer value can't be modified but the pointer can be modified to point to other locations.

* The fourth declaration means that the variable x is a constant pointer to an integer value. It means that the integer value can be changed, but the pointer can't be made to point to anything else.

* The last declaration means that the variable x is a constant pointer to a constant integer which means that neither the pointer can point to a different location nor the integer value can be modified.
Some real-time applications of Embedded Processors are :
 
* Dishwashers
* Automatic passengers cars
* Mobile Phones
* Television
* Medical equipment, etc.
Some examples of the embedded systems to use in Aerospace Applications are:
 
* Autopilot mode
* Engine controllers
* Flight control systems
* Landing and takeoff controller etc.
* A passenger in-flight embedded system
Embedded systems in automotive applications are :
 
* Braking systems
* steer-by-wire systems
* Airbag release systems
* Traffic control systems
* Cruise control applications
* Engine management units etc.
Domestic appliances that use Embedded Systems there are :
 
* Security systems 
* Smart Televisions
* Microwave ovens
* Dishwashers
* Washing machines
* Video recorders and
* Garage door controllers etc.
* ++i instruction uses single machine instruction like INR (Increment Register) to perform the increment.

* For the instruction i+1, it requires to load the value of the variable i and then perform the INR operation on it. Due to the additional load, ++i is faster than the i+1 instruction.
Virtual memory is a means of allocating memory to the processes if there is a shortage of physical memory by using an automatic allocation of storage. The main advantage of using virtual memory is that it is possible to have larger virtual memory than physical memory. It can be implemented by using the technique of paging.
 
Paging works as follows :
 
* Whenever a process needs to be executed, it would be swapped into the memory by using a lazy swapper called a pager.
 
* This decreases the time taken to swap and unnecessary reading of memory pages and reduces the physical memory required.

* The pager tries to guess which page needs to get access to the memory based on a predefined algorithm and swaps that process. This ensures that the whole process is not swapped into the memory, but only the necessary parts of the process are swapped utilizing pages.
Semaphore is actually a variable or abstract data type which controls access to a common resource by multiple processes.
 
Binary semaphore : It can have only two values (0 and 1). The semaphore value is set to 1 by the process in charge, when the resource is available.

Counting semaphore :
 It can have value greater than one. It is used to control access to a pool of resources.
The Nested Vector Interrupt Controller (NVIC) in the Cortex-M processor family is an example of an interrupt controller with extremely flexible interrupt priority management. It enables programmable priority levels, automatic nested interrupt support, along with support for multiple interrupt masking, whilst still being very easy to use by the programmer.
A pointer that is not initialized to any valid address or NULL is considered as wild pointer. Consider the following code fragment -
int *p;

*p = 20;
Here p is not initialized to any valid address and still we are trying to access the address. The p will get any garbage location and the next statement will corrupt that memory location.
Medical Equipments that uses the concept of Embedded Systems :
 
* ECG monitors
* MRI Scanners
* Drug delivery systems
* Ultrasound equipment
* Anaesthesia monitoring systems etc.
Defense Systems that use the concept of Embedded Systems :
 
* Radar systems
* Radio systems
* Missile guidance systems
* Fighter aircraft flight control system
* Target guidance systems etc.
The ARM compilers support inline functions with the keyword __inline. These functions have a small definition and the function body is substituted in each call to the inline function. The argument passing and stack maintenance is skipped and it results in faster code execution, but it increases code size, particularly if the inline function is large or one inline function is used often.
Memory Fragmentation is an issue that arises while using dynamic memory allocation. When we keep allocating and releasing memory spaces over time, in the end, we will get non-contiguous memory blocks that are free, and our in-use variables are scattered everywhere in the RAM. This is called memory fragmentation. This can potentially lead to dynamic memory allocation failure. For example, if you have to allocate an array of 100 integers and there is no contiguous block of memory with that space, this causes a runtime error. This is why we don't use dynamic memory allocation in embedded systems firmware in the first place.
46 .
What are the differences between the following 2 statements #include "..." and #include <...>?
Both declarations specify for the files to be included in the current source file. The difference is in how and where the preprocessor looks for including the files.

For #include "...", the preprocessor just searches for the file in the current directory as where the source file is present and if not found, it proceeds to search in the standard directories specified by the compiler.

Whereas for the #include <...> declaration, the preprocessor looks for the files in the compiler designated directories where the standard library files usually reside.
Memory leak is a phenomenon that occurs when the developers create objects or make use of memory to help memory and then forget to free the memory before the completion of the program. This results in reduced system performance due to the reduced memory availability and if this continues, at one point, the application can crash. These are serious issues for applications involving servers, daemons, etc that should ideally never terminate.
 
Example of Memory Leak :
#include <stdlib.h>
 
void memLeakDemo()
{
   int *p = (int *) malloc(sizeof(int));
 
   /* Some set of statements */
 
   return; /* Return from the function without freeing the pointer p*/
}
 
In this example, we have created pointer p inside the function and we have not freed the pointer before the completion of the function. This causes pointer p to remain in the memory. Imagine 100s of pointers like these. The memory will be occupied unnecessarily and hence resulting in a memory leak.
 
We can avoid memory leaks by always freeing the objects and pointers when no longer required. The above example can be modified as:
#include <stdlib.h>;
 
void memLeakFix()
{
   int *p = (int *) malloc(sizeof(int));
 
   /* Some set of statements */
 
   free(p); // Free method to free the memory allocated to the pointer p
   return;
}
In a nested interrupt system, an interrupt is allowed to any time and anywhere even an ISR is being executed. But, only the highest priority ISR will be executed immediately. The second highest priority ISR will be executed after the highest one is completed.
 
The rules of a nested interrupt system are :
 
* All interrupts must be prioritized.
* After initialization, any interrupts are allowed to occur anytime and anywhere.
* If a low-priority ISR is interrupted by a high-priority interrupt, the high-priority ISR is executed.
* If a high-priority ISR is interrupted by a low-priority interrupt, the high-priority ISR continues executing.
* The same priority ISRs must be executed by time order
Nested interrupt
If you , you should follow this course ““. The course contains video lectures of 18.5-hours length covering all topics like, Microcontroller & Peripheral Driver Development for STM32 GPIO, I2C, SPI, USART using Embedded C.
Let us see an example code to understand this concept. This question is one of the best questions of the embedded C interview question.
 
Suppose in an application, you need to access a fixed memory address. So you need to follow the below steps, these are high-level steps.
//Memory address, you want to access
#define RW_FLAG 0x1FFF7800
//Pointer to access the Memory address
volatile uint32_t *flagAddress = NULL;
//variable to stored the read value
uint32_t readData = 0;
//Assign addres to the pointer
flagAddress = (volatile uint32_t *)RW_FLAG;
//Read value from memory
* flagAddress = 12; // Write
//Write value to the memory
readData = * flagAddress;
In the embedded system, I2C and SPI both play an important role. Both communication protocols are the example of synchronous communication but still, both have some important differences.
 
The important difference between the I2C and SPI communication protocol.
 
* I2C supports half-duplex while SPI is full-duplex communication.
* I2C is slower as compared to the SPI communication.
* I2C draws more power than SPI.
* I2C is less susceptible to noise than SPI.
* I2C is cheaper to implement than the SPI communication protocol.
* I2C supports arbitration while SPI does not support the arbitration.
* I2C support the clock stretching while SPI does not support the clock stretching.
* I2C can be locked up by one device that fails to release the communication bus.
* I2C has some extra overhead due to start and stop bits.
* I2C is better for long-distance while SPI is better for the short distance.
* In the last I2C developed by NXP while SPI by Motorola.
* I2C requires only two-wire for communication while SPI requires three or four-wire for communication (depends on requirement).
* I2C work on wire and logic and it has a pull-up resistor while there is no requirement of a pull-up resistor in case of the SPI.
* In I2C communication we get the acknowledgment bit after each byte, it is not supported by the SPI communication protocol.
* I2C ensures that data sent is received by the slave device while SPI does not verify that data is received correctly.
* I2C supports multi-master communication while multi-master communication is not supported by the SPI.
* One great difference between I2C and SPI is that I2C supports multiple devices on the same bus without any additional select lines (work based on device address) while SPI requires additional signal (slave select lines) lines to manage multiple devices on the same bus.
 
An enum in C is a user-defined data type. It consists set of named constant integers. Using the enum keyword, we can declare an enumeration type by using the enumeration tag (optional) and a list of named integer.
 
Basically, we used the enum to increase the code readability and with enum easy to debug the code as compared to symbolic constant (macro). The most important property of enum is that it follows the scope rule and the compiler automatically assigns the value to its member constant.
 
Note : A variable of enumeration type stores one of the values of the enumeration list defined by that type.
 
Syntax of enum :
enum Enumeration_Tag { Enumeration_List };
The Enumeration_Tag specifies the enumeration type name.
 
The Enumeration_List is a comma-separated list of named constant.
 
Example :
enum FLASH_ERROR { DEFRAGMENT_ERROR, BUS_ERROR};
We can convert little-endian to big-endian or vice versa using the C programs. So let us see few ways to convert one endian to another.
#include <stdio.h>
#include <inttypes.h>
//Function to change one endian to another
uint32_t ChangeEndianness(uint32_t u32Value)
{
    uint32_t u32Result = 0;
    u32Result |= (u32Value & 0x000000FF) << 24;
    u32Result |= (u32Value & 0x0000FF00) << 8;
    u32Result |= (u32Value & 0x00FF0000) >> 8;
    u32Result |= (u32Value & 0xFF000000) >> 24;
    return u32Result;
}
int main()
{
    uint32_t u32CheckData  = 0x11223344;
    uint32_t u32ResultData =0;
    //swap the data
    u32ResultData = ChangeEndianness(u32CheckData);
    //converted data
    printf("0x%x\n",u32ResultData);
    return 0;
}
 
Output :
0x44332211
The RS232 and RS485 is an old serial interface. Both serial interfaces are the standard for data communication. This question is also very important and generally ask by an interviewer.
 
Some important difference between the RS232 and RS485

Parameter RS232 RS485
Line configuration Single –ended differential
Numbers of devices 1 transmitter 1 receiver 32 transmitters 32 receivers
Mode of operation Simplex or full duplex Simplex or half duplex
Maximum cable length 50 feet 4000 feet
Maximum data rate 20 Kbits/s 10 Mbits/s
signaling unbalanced balanced
Typical logic levels +-5 ~ +-15V +-1.5 ~ +-6V
Minimum receiver input impedance 3 ~ 7 K-ohm 12 K-ohm
Receiver sensitivity +-3V +-200mV
Bit Rate Baud Rate
Bit rate is the number of bits per second. Baud rate is the number of signal units per second.
It determines the number of bits traveled per second. It determines how many times the state of a signal is changing.
Cannot determine the bandwidth. It can determine how much bandwidth is required to send the signal.
This term generally used to describe the processor efficiency. This term generally used to describe the data transmission over the channel.
Bit rate = baud rate x the number of bits per signal unit Baud rate = bit rate / the number of bits per signal unit
This can be achieved by involving bit manipulation techniques - Shift left operator as shown below:
#include<stdio.h>
void main(){
    int num;
    printf(“Enter number: ”);
    scanf(“%d”,&num);
    printf(“%d”, (num<<3)+num);
}
Using Extra Memory Space :
int num1=20, num2=30, temp;
temp = num1;
num1 = num2;
num2 = temp;
Using Arithmetic Operators :
int num1=20, num2=30;
num1=num1 + num2;
num2=num1 - num2;
num1=num1 - num2;
Using Bit-Wise Operators :
int num1=20, num2=30;
num1=num1 ^ num2;
num2=num2 ^ num1;
num1=num1 ^ num2;
Using One-liner Bit-wise Operators :
int num1=20, num2=30;
num1^=num2^=num1^=num2;
The order of evaluation here is right to left.
 
Using One-liner Arithmetic Operators :
int num1=20, num2=30;
num1 = (num1+num2)-(num2=num1);
Here the order of evaluation is from left to right.
According to C standard, there are four storage duration, static, thread (C11), automatic, and allocated. The storage duration determines the lifetime of the object.
 
The static memory allocation :
Static Allocation means, an object has an external or internal linkage or declared with static storage-class. It’s initialized only once, before program startup and its lifetime is throughout the execution of the program. A global and static variable is an example of static memory allocation.
 
The dynamic memory allocation :
In C language, there are a lot of library functions (malloc, calloc, or realloc,..) which are used to allocate memory dynamically. One of the problems with dynamically allocated memory is that it is not destroyed by the compiler itself that means it is the responsibility of the user to deallocate the allocated memory.
 
When we allocate the memory using the memory management function, they return a pointer to the allocated memory block and the returned pointer is pointing to the beginning address of the memory block. If there is no space available, these functions return a null pointer.
A malloc and calloc are memory management functions. They are used to allocate memory dynamically. Basically, there is no actual difference between calloc and malloc except that the memory that is allocated by calloc is initialized with 0.
 
In C language,calloc function initialize the all allocated space bits with zero but malloc does not initialize the allocated memory. These both function also has a difference regarding their number of arguments, malloc takes one argument but calloc takes two.
The realloc function is used to resize the allocated block of memory. It takes two arguments first one is a pointer to previously allocated memory and the second one is the newly requested size.
 
The calloc function first deallocates the old object and allocates again with the newly specified size. If the new size is lesser to the old size, the contents of the newly allocated memory will be the same as prior but if any bytes in the newly created object goes beyond the old size, the values of the exceeded size will be indeterminate.
 
Syntax :
void *realloc(void *ptr, size_t size);
The syntax for declaring function pointer is very straightforward. It seems difficult in beginning but once you are familiar with function pointer then it becomes easy.
 
The declaration of a pointer to a function is similar to the declaration of a function. That means the function pointer also requires a return type, declaration name, and argument list. One thing that you need to remember here is, whenever you declare the function pointer in the program then the declaration name is preceded by the * (Asterisk) symbol and enclosed in parenthesis.
 
For example : 
void ( *fpData )( int );

 

For a better understanding, let’s take an example to describe the declaration of a function pointer in the C program.
Ex: 
void ( *pfDisplayMessage) (const char *);
In the above expression, pfDisplayMessage is a pointer to a function taking one argument, const char *, and returns void.
 
When we declare a pointer to function in c then there is a lot of importance of the bracket. If in the above example, I remove the bracket, then the meaning of the above expression will be change and it becomes void *pfDisplayMessage (const char *). It is a declaration of a function that takes the const character pointer as arguments and returns a void pointer.
Pass By Value:
 
* In this method, the value of the variable is passed. Changes made to formal will not affect the actual parameters.
* Different memory locations will be created for both variables.
* Here there will be a temporary variable created in the function stack which does not affect the original variable.

Pass By Reference :
 
* In Pass by reference, an address of the variable is passed to a function.
* Whatever changes made to the formal parameter will affect the value of actual parameters(a variable whose address is passed).
* Both formal and actual parameters shared the same memory location.
* it is useful when you required to returns more than 1 value.
Declaration of a variable in C : 
A variable declaration only provides sureness to the compiler at the compile time that variable exists with the given type and name, so that compiler proceeds for further compilation without needing all detail of this variable. When we declare a variable in C language, we only give the information to the compiler, but there is no memory reserve for it. It is only a reference, through which we only assure the compiler that this variable may be defined within the function or outside of the function.
 
Note : We can declare a variable multiple times but defined only once.
Ex : 
extern int data;
extern int foo(int, int);
int fun(int, char); // extern can be omitted for function declarations
 
Definition of variable in C :
The definition is action to allocate storage to the variable. In another word, we can say that variable definition is the way to say the compiler where and how much to create the storage for the variable generally definition and declaration occur at the same time but not almost.
 
Ex : 
int data;
int foo(int, int) { }
Note : When you define a variable then there is no need to declare it but vice versa is not applicable.
There are the following places where we need to use the const keyword in the programs.
 
* In case of call by reference, if you don’t want to change the value of the passed variable. ex:
int PrintData ( const char *pcMessage);
* In some places, const is better than macro because const is handled by the compiler and has a type checking.

* In the case of the I/O and memory-mapped register, const is used with the volatile qualifier for efficient access. for ex:
const volatile uint32_t *DEVICE_STATUS = (uint32_t *) 0x80102040;
* When you don’t want to change the value of an initialized variable.