Posted on Leave a comment

Introduction to Numerical Integration using C

Numerical integration plays a crucial role in many applications related to embedded systems, such as control systems, signal processing, and digital filters. However, implementing numerical integration algorithms on embedded systems presents unique challenges due to limited computing resources such as memory, processing power, and clock speed.

To address these challenges, developers must carefully choose numerical integration methods that are well-suited for embedded systems. The choice of method depends on the specific requirements of the problem being solved and the available computing resources.

Some of the most commonly used numerical integration methods in embedded systems include the Trapezoidal Rule, Simpson’s Rule, and the Midpoint Rule. These methods are relatively simple to implement and require minimal memory and processing power, making them well-suited for use in low-power embedded systems.

In addition to these basic methods, more advanced numerical integration methods can also be used in embedded systems with larger amounts of memory and processing power. These methods include Gaussian Quadrature, Monte Carlo Integration, Romberg Integration, Adaptive Quadrature, Runge-Kutta Method, Newton-Cotes Formulas, and Chebyshev Quadrature.

Implementing numerical integration algorithms on embedded systems requires careful consideration of factors such as memory usage, processing power, and clock speed. Developers must also optimize their code to ensure efficient use of available resources and avoid memory leaks, stack overflows, and other common pitfalls.

Despite these challenges, numerical integration remains an essential tool for many applications related to embedded systems. With the right choice of method and careful optimization of code, developers can achieve high accuracy and efficiency in numerical integration calculations on embedded systems.

The Need for Numerical Integration

In many cases, it is not possible to find the exact analytical solution for a definite integral, either due to the complexity of the function or the limits of integration. In such cases, numerical integration provides a useful alternative for approximating the integral by dividing the interval into smaller subintervals, and then computing the area under the curve for each subinterval.

Here’s a list of functions that can solve definite integrals in embedded C:

  • Trapezoidal Rule
  • Simpson’s Rule
  • Gaussian Quadrature
  • Monte Carlo Integration
  • Romberg Integration
  • Adaptive Quadrature
  • Runge-Kutta Method (for solving ODEs)

Each of these methods has its advantages and disadvantages, and the choice of which method to use depends on the specific problem being solved and the available computing resources.

The Trapezoidal Rule and Simpson’s Rule are commonly used in embedded systems with limited RAM. These methods are relatively simple to implement and require minimal memory usage, making them well-suited for use in microcontrollers with limited resources. Additionally, these methods provide reasonably accurate results for many common functions, which further makes them popular choices for numerical integration in embedded systems.

Depending on the availability of RAM, different methods are available.

If the RAM is up to 32K, then integration methods considered, such as:

The Trapezoidal Rule and Simpson’s Rule are commonly used in embedded systems with limited RAM. These methods are relatively simple to implement and require minimal memory usage, making them well-suited for use in microcontrollers with limited resources. Additionally, these methods provide reasonably accurate results for many common functions, which further makes them popular choices for numerical integration in embedded systems.

If the RAM is increased to 60K, then some of the more computationally intensive integration methods can also be considered, such as:

  • Gaussian Quadrature: This method uses a weighted sum of function values at specific points to approximate the integral. It can provide higher accuracy than the Trapezoidal Rule and Simpson’s Rule for certain functions, but requires more memory and computational resources.
  • Monte Carlo Integration: This method uses random sampling to approximate the integral. It can provide accurate results for high-dimensional integrals, but can require a large number of function evaluations to achieve acceptable accuracy.
  • Romberg Integration: This method uses a recursive procedure to improve the accuracy of the Trapezoidal Rule. It can provide higher accuracy than the Trapezoidal Rule and Simpson’s Rule, but requires more memory and computational resources.
  • Adaptive Quadrature: This method adaptively subdivides the integration interval into smaller subintervals to achieve a desired level of accuracy. It can provide accurate results for functions with complex behavior, but can require a large number of function evaluations for high accuracy.

Note that the choice of integration method also depends on the specific requirements of the application and the available computing resources.

With an increased RAM of 512K, there are many numerical integration methods that can be used. Some of the most popular methods include:

  1. Gaussian Quadrature: This method provides high accuracy for a wide range of integrals but requires more memory than the Trapezoidal Rule or Simpson’s Rule.
  2. Monte Carlo Integration: This method involves randomly sampling points within the integration domain and is particularly useful for high-dimensional integrals. However, it requires a larger amount of memory than the Trapezoidal Rule or Simpson’s Rule due to the need to store a large number of random points.
  3. Romberg Integration: This method is a recursive extension of the Trapezoidal Rule and can achieve higher accuracy for a given number of function evaluations. However, it requires more memory to store the intermediate results of each recursive step.
  4. Adaptive Quadrature: This method dynamically adjusts the number of function evaluations based on the local behavior of the integrand, which can lead to higher accuracy for a given number of function evaluations. However, it requires more memory to store the intermediate results of each recursive step.
  5. Runge-Kutta Method: This method is commonly used for solving differential equations but can also be used for numerical integration. It provides higher accuracy than simple integration methods such as Trapezoidal Rule and Simpson’s Rule, but requires more memory to store the intermediate results of each step.
  6. Newton-Cotes Formulas: These are a family of numerical integration formulas that can achieve higher accuracy than simple integration methods. However, they require more memory to store the intermediate results of each step.
  7. Chebyshev Quadrature: This method uses the roots of Chebyshev polynomials to approximate the integral and can achieve high accuracy with fewer function evaluations than other methods. However, it requires more memory to store the coefficients of the Chebyshev polynomials.

Overall, the choice of numerical integration method will depend on the specific requirements of the problem being solved and the available computing resources. With a larger amount of RAM, more memory-intensive methods can be used, which can provide higher accuracy for a given number of function evaluations.

Posted on Leave a comment

The command-line argument in C

Lets say if we want our program to work with other programs and other program call our program. Then just like how we provide arguments in function call; we can call our program and the arguments can be given using the command line.

#include <stdio.h>
#include <string.h>

int main(int argc, char* argv[]){
	int arr[5] = {1,2,3,4,5};

	printf("the argument number of of main argc : %d\n",argc);
	printf("The first argument : %s\n",argv[1]);
	
	return 0;
}


argc contains the number of argument passed to the main function.

argv is the argument vector. Contains the argument given from index 1 and above.

index 0 of argv is the file name of the function.

Posted on Leave a comment

Union

Union is another user defined data type.

The difference between union and struct is that; struct uses a contagious memory allocation for each individual item inside it; whereas union uses the size of biggest element for allocation of memory.

union segment1 {
int x1;
char y2;
};

// sizeof segement1 union is 4 bytes 

struct segment2{
int x1;
char y2;
};

// sizeof segement1 union is 8 bytes 

Posted on Leave a comment

Convert program written in C into assembly

Sometimes to understand code , you need to look into assembly language. I write mostly using C. So sometimes to better understand the program i converte the program into assembly. The syntax changes from machine to machine.

Here is the program that i will use to convert to assembly using two different machine. One is ARM based and the other is x86_64.

#include <stdio.h>

struct CarDetail{
int CarNumber;
char CarOptions;
};

int main() {
struct CarDetail maruti_alto;

maruti_alto.CarNumber = 123;
maruti_alto.CarOptions = 'L';

printf("Maruti Alto Details\nNumber = \t%d\nOptions = \t%c",maruti_alto.CarNumber,maruti_alto.CarOptions);
return 0;
}

ARM

If i convert the above program using ARM based machine using command

gcc main.c -S -o main.s
        .arch armv6
	.file	"main.c"
	.text
	.section	.rodata
	.align	2
.LC0:
	.ascii	"Maruti Alto Details\012Number = \011%d\012Options ="
	.ascii	" \011%c\000"
	.text
	.align	2
	.global	main
	.arch armv6
	.syntax unified
	.arm
	.fpu vfp
	.type	main, %function
main:
	@ args = 0, pretend = 0, frame = 8
	@ frame_needed = 1, uses_anonymous_args = 0
	push	{fp, lr}
	add	fp, sp, #4
	sub	sp, sp, #8
	mov	r3, #123
	str	r3, [fp, #-12]
	mov	r3, #76
	strb	r3, [fp, #-8]
	ldr	r3, [fp, #-12]
	ldrb	r2, [fp, #-8]	@ zero_extendqisi2
	mov	r1, r3
	ldr	r0, .L3
	bl	printf
	mov	r3, #0
	mov	r0, r3
	sub	sp, fp, #4
	@ sp needed
	pop	{fp, pc}
.L4:
	.align	2
.L3:
	.word	.LC0
	.size	main, .-main
	.ident	"GCC: (Raspbian 8.3.0-6+rpi1) 8.3.0"
	.section	.note.GNU-stack,"",%progbits

If we want to remove relative addressing modes and make it very simple you can use -fomit-frame-pointer option

gcc main.c -S -fomit-frame-pointer -o main-omit-fp.s
        .arch armv6
	.file	"main.c"
	.text
	.section	.rodata
	.align	2
.LC0:
	.ascii	"Maruti Alto Details\012Number = \011%d\012Options ="
	.ascii	" \011%c\000"
	.text
	.align	2
	.global	main
	.arch armv6
	.syntax unified
	.arm
	.fpu vfp
	.type	main, %function
main:
	@ args = 0, pretend = 0, frame = 8
	@ frame_needed = 0, uses_anonymous_args = 0
	str	lr, [sp, #-4]!
	sub	sp, sp, #12
	mov	r3, #123
	str	r3, [sp]
	mov	r3, #76
	strb	r3, [sp, #4]
	ldr	r3, [sp]
	ldrb	r2, [sp, #4]	@ zero_extendqisi2
	mov	r1, r3
	ldr	r0, .L3
	bl	printf
	mov	r3, #0
	mov	r0, r3
	add	sp, sp, #12
	@ sp needed
	ldr	pc, [sp], #4
.L4:
	.align	2
.L3:
	.word	.LC0
	.size	main, .-main
	.ident	"GCC: (Raspbian 8.3.0-6+rpi1) 8.3.0"
	.section	.note.GNU-stack,"",%progbits

X86_64

gcc main.c -S -o main.s
	.file	"main.c"
	.text
	.section	.rodata
	.align 8
.LC0:
	.string	"Maruti Alto Details\nNumber = \t%d\nOptions = \t%c"
	.text
	.globl	main
	.type	main, @function
main:
.LFB0:
	.cfi_startproc
	pushq	%rbp
	.cfi_def_cfa_offset 16
	.cfi_offset 6, -16
	movq	%rsp, %rbp
	.cfi_def_cfa_register 6
	subq	$16, %rsp
	movl	$123, -8(%rbp)
	movb	$76, -4(%rbp)
	movzbl	-4(%rbp), %eax
	movsbl	%al, %edx
	movl	-8(%rbp), %eax
	movl	%eax, %esi
	leaq	.LC0(%rip), %rdi
	movl	$0, %eax
	call	printf@PLT
	movl	$0, %eax
	leave
	.cfi_def_cfa 7, 8
	ret
	.cfi_endproc
.LFE0:
	.size	main, .-main
	.ident	"GCC: (Debian 8.3.0-6) 8.3.0"
	.section	.note.GNU-stack,"",@progbits
gcc main.c -S -fomit-frame-pointer -o main-omit-fp.s
	.file	"main.c"
	.text
	.section	.rodata
	.align 8
.LC0:
	.string	"Maruti Alto Details\nNumber = \t%d\nOptions = \t%c"
	.text
	.globl	main
	.type	main, @function
main:
.LFB0:
	.cfi_startproc
	subq	$24, %rsp
	.cfi_def_cfa_offset 32
	movl	$123, 8(%rsp)
	movb	$76, 12(%rsp)
	movzbl	12(%rsp), %eax
	movsbl	%al, %edx
	movl	8(%rsp), %eax
	movl	%eax, %esi
	leaq	.LC0(%rip), %rdi
	movl	$0, %eax
	call	printf@PLT
	movl	$0, %eax
	addq	$24, %rsp
	.cfi_def_cfa_offset 8
	ret
	.cfi_endproc
.LFE0:
	.size	main, .-main
	.ident	"GCC: (Debian 8.3.0-6) 8.3.0"
	.section	.note.GNU-stack,"",@progbits
Posted on Leave a comment

Struct

Variables are used for storage of individual data elements. But what if we want to group different data element and use them as a individual element.

//Example 1
#include <stdio.h>
struct tag_name {
int x;
char y;
float z;
}element1;

int main() {
struct tag_name element2;

element1.x = 1;
element1.y = 'a';
element1.z = 3.14;

element2.x = eletment1.x;
element2.y = eletment1.y;
element2.z = eletment1.z;

printf("element 1\nx = %d \ty = %c \tz= %f\nelement 2\nx = %d \ty = %c \tz= %f\n", element1.x,element1.y,element1.z,element2.x,element2.y,element2.z);
return 0;
}
Struct example 1 Code Output
Struct example 1 Code Output

Nesting Struct is a way of creating more complex data structure.

// Example 2
#include <stdio.h>

typedef struct {
int x;
char y;
} point;

int main() {
point p1, *ptPtr;

p1.x = 9;
p1.y = 'x';

printf("p1.x = %d\n",p1.x);
printf("p1.y = %c\n",p1.y);

struct line {
point pt1;
point pt2;
};

struct line l1, *l1Ptr;

l1Ptr = &l1;
ptPtr = &l1.pt1;

l1.pt1.x = 44;
l1.pt1.y = 'r';

printf("l1.pt1.x = %d\n",l1.pt1.x);
printf("l1.pt1.y = %c\n",l1.pt1.y);

printf("l1.pt1.x = %d\n",l1Ptr->pt1.x);
printf("l1.pt1.y = %c\n",ptPtr->y);


return 0;
}


Example 2 output

Posted on Leave a comment

First Program in C language

The very first program in every language is “Hello World”. You can compile this program very easily.

// a single line comment

/*
* A multi line comment
*/

#include <stdio.h> // Header File

int main()         // Marks the start of program 
{ 
    printf("Hello World!\n"); // Prints the "hello world" statement onto the
                              // serial output device such as monitor.
return 0;
}