Module division The "division" in module division means planning, which refers to how to reasonably divide a large software into a series of independent functional parts that work together to meet the needs of the system. As a structured programming language, C language mainly divides modules based on functions (division based on functions becomes a mistake in object-oriented design, Newton's law encounters the theory of relativity). C language modular programming requires the following understanding of the following concepts: (1) A module is a combination of a .c file and a .h file. The header file (.h) is a declaration of the module interface; (2) External functions and data provided by a module to other modules must be declared in the .h file with the extern keyword; (3) Functions and global variables within a module must be declared at the beginning of the .c file with the static keyword; (4) Never define variables in a .h file! The difference between defining a variable and declaring a variable is that definition will generate memory allocation operations, which is a concept in the assembly stage; while declaration only tells the module containing the declaration to find external functions and variables from other modules during the linking stage. For example: /*module1.h*/ int a = 5; /* Define int a in module 1's .h file */ /*module1 .c*/ #include "module1.h" /* Include module 1's .h file in module 1*/ /*module2 .c*/ #include "module1.h" /* Include module 1's .h file in module 2*/ /*module3 .c*/ #include "module1.h" /* Include module 1's .h file in module 3*/ The result of the above program is that the integer variable a is defined in modules 1, 2, and 3, and a corresponds to different address units in different modules. There is no need for such a program in the world. The correct way is: /*module1.h*/ extern int a; /* declare int a in module 1's .h file */ /*module1 .c*/ #include "module1.h" /* include module 1's .h file in module 1*/ int a = 5; /* define int a in module 1's .c file */ /*module2 .c*/ #include "module1.h" /* include module 1's .h file in module 2*/ /*module3 .c*/ #include "module1.h" /* include module 1's .h file in module 3*/ In this way, if modules 1, 2, and 3 operate a, they correspond to the same memory unit. An embedded system usually includes two types of modules: (1) hardware driver module, one module for each specific hardware; (2) software function module, the division of which should meet the requirements of low coupling and high cohesion. Multi-task or single-task The so-called "single-task system" means that the system cannot support multi-task concurrent operation and executes one task in a macro-serial manner. A multitasking system can execute multiple tasks "simultaneously" in macro-parallel (or possibly serially at the micro level). The concurrent execution of multiple tasks usually depends on a multitasking operating system (OS). The core of a multitasking OS is the system scheduler, which uses a task control block (TCB) to manage task scheduling functions. The TCB includes information such as the current state of the task, priority, events or resources to be waited for, the starting address of the task program code, and the initial stack pointer. The scheduler uses this information when the task is activated. In addition, the TCB is also used to store the "context" of the task. The context of a task is all the information to be saved when a running task is stopped. Usually, the context is the current state of the computer, that is, the contents of each register. When a task switch occurs, the context of the currently running task is stored in the TCB, and the context of the task to be executed is taken out of its TCB and placed in each register. Typical examples of embedded multitasking OS include Vxworks, ucLinux, etc. Embedded OS is not a distant and unattainable thing. We can use less than 1000 lines of code to implement a simplest OS kernel for 80186 processor. The author is preparing to do this work and hopes to contribute his experience to everyone. Whether to choose multi-tasking or single-tasking depends on whether the software system is large. For example, most mobile phone programs are multi-tasking, but some PHS protocol stacks are single-tasking and have no operating system. Their main program calls the processing program of each software module in turn to simulate a multi-tasking environment. The typical architecture of a single-task program (1) starts execution from the specified address when the CPU is reset; (2) jumps to the assembly code startup to execute; (3) jumps to the user main program main to execute, and completes the following in main: a. Initialize each hardware device; b. Initialize each software module; c. Enter a dead loop (infinite loop) and call the processing function of each module. The user main program and the processing function of each module are all completed in C language. The user's main program will eventually enter an infinite loop. The preferred solution is: while(1) { } Some programmers write like this: for(;;) { } This syntax does not clearly express the meaning of the code. We cannot see anything from for(;;). Only when we understand that for(;;) means an unconditional loop in C language can we understand its meaning. The following are several "famous" infinite loops: (1) The operating system is an infinite loop; (2) The WIN32 program is an infinite loop; (3) The embedded system software is an infinite loop; (4) The thread processing function of a multi-threaded program is an infinite loop. You may argue and say loudly: "Nothing is absolute. 2, 3, and 4 may not be infinite loops." Yes, you are right, but you will not get flowers and applause. In fact, this is a meaningless horn, because the world never needs a WIN32 program that shouts for the OS to kill it after processing a few messages, does not need an embedded system that terminates itself as soon as it starts to run, and does not need to start a thread that kills itself when it does something. Sometimes, being too strict creates trouble instead of convenience. Have you seen that the five-layer TCP/IP protocol stack has surpassed the rigorous ISO/OSI seven-layer protocol stack and has become the de facto standard? Netizens often discuss: printf("%d,%d",++i,i++); /* What is the output? */ c = a+++b; /* c=? */ and other similar questions. Faced with these questions, we can only sigh from the bottom of our hearts: there are still many meaningful things in the world waiting for us to digest the food we eat. In fact, embedded systems have to run until the end of the world. Interrupt service routines Interrupts are an important part of embedded systems, but interrupts are not included in standard C. Many compiler developers have added support for interrupts to standard C, providing new keywords for marking interrupt service routines (ISRs), such as __interrupt, #programinterrupt, etc. When a function is defined as an ISR, the compiler will automatically add interrupt scene stack push and pop codes required by the interrupt service routine for the function. The interrupt service routine must meet the following requirements: (1) It cannot return a value; (2) It cannot pass parameters to the ISR; (3) The ISR should be as short and concise as possible; (4) printf(char * lpFormatString,…) function will cause reentrancy and performance problems and cannot be used in ISR. In the development of a certain project, we designed a queue. In the interrupt service routine, we just added the interrupt type to the queue. In the infinite loop of the main program, we continuously scanned the interrupt queue to see if there was an interrupt. If there was, we took out the first interrupt type in the queue and processed it accordingly. /* Queue for storing interrupts*/ typedef struct tagIntQueue { int intType; /* interrupt type*/ struct tagIntQueue *next; }IntQueue; IntQueue lpIntQueueHead; __interrupt ISRexample () { int intType; intType = GetSystemType(); QueueAddTail(lpIntQueueHead, intType);/* Add a new interrupt to the end of the queue*/ } In the main program loop, determine whether there is an interrupt: While(1) { If( !IsIntQueueEmpty() ) { intType = GetFirstInt(); switch(intType) /* Doesn't it look like the message parsing function of a WIN32 program? */ { /* Yes, our interrupt type parsing is very similar to message driver*/ case xxx: /* Let's call it "interrupt driver", right? */ … break; case xxx: … break; … } } } The interrupt service routine designed according to the above method is very small, and the actual work is left to the main program to perform. Hardware driver module A hardware driver module should usually include the following functions: (1) Interrupt service routine ISR (2) Hardware initialization a. Modify registers and set hardware parameters (such as UART should set its baud rate, AD/DA equipment should set its sampling rate, etc.); b. Write the interrupt service program entry address to the interrupt vector table: /* Set interrupt vector table*/ m_myPtr = make_far_pointer(0l); /* Return void far pointer void far * */ m_myPtr += ITYPE_UART; /* ITYPE_UART: UART interrupt service routine*/ /* Offset relative to the first address of the interrupt vector table*/ *m_myPtr = &UART _Isr; /* UART _Isr: UART interrupt service routine*/ (3) Set the CPU control line for the hardware a. If the control line can be used as PIO (programmable I/O) and control signal, set the corresponding register inside the CPU to use it as a control signal; b. Set the interrupt mask bit for the device inside the CPU and set the interrupt mode (level trigger or edge trigger). (4) Provide a series of operation interface functions for the device. For example, for LCD, its driver module should provide functions such as drawing pixels, drawing lines, drawing matrices, and displaying character dot matrix; and for real-time clock, its driver module needs to provide functions such as getting time and setting time. Object-oriented C In object-oriented languages, the concept of class appears. A class is a collection of specific operations on specific data. Classes contain two categories: data and operations. The struct in C language is just a collection of data. We can use function pointers to simulate the struct as a "class" containing data and operations. The following C program simulates a simplest "class": #ifndef C_Class #define C_Class struct #endif C_Class A { C_Class A *A_this; /* this pointer*/ void (*Foo)(C_Class A *A_this); /* behavior: function pointer*/ int a; /* data*/ int b; }; We can use C language to simulate the three characteristics of object-oriented: encapsulation, inheritance and polymorphism, but more often, we just need to encapsulate data and behavior to solve the problem of chaotic software structure. The purpose of C simulating object-oriented thinking is not to simulate the behavior itself, but to solve the problem of scattered overall framework structure of the program and disconnection of data and functions when programming in C language in some cases. We will see such examples in subsequent chapters. Summary This article introduces the knowledge of embedded system programming software architecture, mainly including module division, multi-task or single-task selection, typical architecture of single-task program, interrupt service program, hardware driver module design, etc., and gives the main elements of an embedded system software from a macro perspective. Please remember: software structure is the soul of software! A program with a chaotic structure is ugly, and it is extremely difficult to debug, test, maintain, and upgrade it.Set the interrupt mask bit for the device inside the CPU and set the interrupt mode (level trigger or edge trigger). (4) Provide a series of operation interface functions for the device. For example, for LCD, its driver module should provide functions such as drawing pixels, drawing lines, drawing matrices, and displaying character dot matrix; and for real-time clock, its driver module needs to provide functions such as getting time and setting time. Object-oriented C In object-oriented languages, the concept of class appears. A class is a collection of specific operations on specific data. Classes contain two categories: data and operations. The struct in C language is just a collection of data. We can use function pointers to simulate the struct as a "class" containing data and operations. The following C program simulates a simplest "class": #ifndef C_Class #define C_Class struct #endif C_Class A { C_Class A *A_this; /* this pointer*/ void (*Foo)(C_Class A *A_this); /* behavior: function pointer*/ int a; /* data*/ int b; }; We can use C language to simulate the three characteristics of object-oriented: encapsulation, inheritance and polymorphism, but more often, we just need to encapsulate data and behavior to solve the problem of chaotic software structure. The purpose of C simulating object-oriented thinking is not to simulate the behavior itself, but to solve the problem of scattered overall framework structure of the program and disconnection of data and functions when programming in C language in some cases. We will see such examples in subsequent chapters. Summary This article introduces the knowledge of embedded system programming software architecture, mainly including module division, multi-task or single-task selection, typical architecture of single-task program, interrupt service program, hardware driver module design, etc., and gives the main elements of an embedded system software from a macro perspective. Please remember: software structure is the soul of software! A program with a chaotic structure is ugly, and it is extremely difficult to debug, test, maintain, and upgrade it.Set the interrupt mask bit for the device inside the CPU and set the interrupt mode (level trigger or edge trigger). (4) Provide a series of operation interface functions for the device. For example, for LCD, its driver module should provide functions such as drawing pixels, drawing lines, drawing matrices, and displaying character dot matrix; and for real-time clock, its driver module needs to provide functions such as getting time and setting time. Object-oriented C In object-oriented languages, the concept of class appears. A class is a collection of specific operations on specific data. Classes contain two categories: data and operations. The struct in C language is just a collection of data. We can use function pointers to simulate the struct as a "class" containing data and operations. The following C program simulates a simplest "class": #ifndef C_Class #define C_Class struct #endif C_Class A { C_Class A *A_this; /* this pointer*/ void (*Foo)(C_Class A *A_this); /* behavior: function pointer*/ int a; /* data*/ int b; }; We can use C language to simulate the three characteristics of object-oriented: encapsulation, inheritance and polymorphism, but more often, we just need to encapsulate data and behavior to solve the problem of chaotic software structure. The purpose of C simulating object-oriented thinking is not to simulate the behavior itself, but to solve the problem of scattered overall framework structure of the program and disconnection of data and functions when programming in C language in some cases. We will see such examples in subsequent chapters. Summary This article introduces the knowledge of embedded system programming software architecture, mainly including module division, multi-task or single-task selection, typical architecture of single-task program, interrupt service program, hardware driver module design, etc., and gives the main elements of an embedded system software from a macro perspective. Please remember: software structure is the soul of software! A program with a chaotic structure is ugly, and it is extremely difficult to debug, test, maintain, and upgrade it.
Well said: Software structure is the soul of software! A program with a chaotic structure is ugly, and it is extremely difficult to debug, test, maintain, and upgrade.
Details
Published on 2018-12-9 12:11
Well said: Software structure is the soul of software! A program with a chaotic structure is ugly, and it is extremely difficult to debug, test, maintain, and upgrade.