Detailed explanation of the method steps of C language in programming a variety of embedded systems

C language is a general-purpose computer programming language that is widely used. The C language is designed to provide a programming language that can be easily compiled, processed with low-level memory, generated with a small amount of machine code, and run without any runtime environment support.

Although the C language provides many low-level processing functions, it still maintains good cross-platform features. The C language program written in a standard specification can be compiled on many computer platforms, even including some embedded processors ( MCU) and supercomputer and other operating platforms.

In the 1980s, in order to avoid differences in the C language grammar used by various developers, the National Bureau of Standards established a complete set of international standard grammar for the C language, called ANSI C, as the initial standard of the C language.

C language embedded system programming considerations

Different from the general form of software programming, embedded system programming is built on a specific hardware platform, which is bound to require its programming language to have strong hardware direct operation capability. Undoubtedly, assembly language has such qualities. However, due to the complexity of the assembly language development process, it is not a general choice for embedded system development. In contrast, the C language, a "high-level, low-level" language, is the best choice for embedded system development. In the development process of embedded system project, the author feels the exquisiteness of C language again and again, and indulges the convenience brought by C language to embedded development.

The hardware platform of most embedded systems. It consists of two parts:

(1) A general-purpose processor-centric protocol processing module for processing network control protocols;

(2) A digital signal processor (DSP)-centric signal processing module for modulation, demodulation, and digital/analog signal conversion.

The discussion in this article focuses on general-purpose processor-centric protocol processing modules because it involves more specific C programming skills. DSP programming focuses on specific digital signal processing algorithms, mainly related to the field of communication, and is not the focus of this article.

Focusing on the discussion of common embedded system C programming skills, the system's protocol processing module does not choose a special CPU, but chooses the well-known CPU chip --80186. Every reader who has studied "Microcomputer Principle" should The chip has a basic understanding and is familiar with its instruction set. The word length of the 80186 is 16 bits, and the memory space that can be addressed is 1MB, only the real address mode. The pointer generated by C language compilation is 32 bits (double word), the upper 16 bits are segment addresses, and the lower 16 bits are compiled in segments, and a segment is up to 64 KB.

The FLASH and RAM in the protocol processing module are almost all necessary equipment for each embedded system. The former is used to store programs, while the latter is used to store instructions and data storage locations. The FLASH and RAM selected by the system have a bit width of 16 bits, which is consistent with the CPU.

The real clock chip can be timed for the system, giving the current year, month, day and specific time (hours, minutes, seconds and milliseconds). It can be set to give an interrupt to the CPU or set the alarm time when the time comes. The CPU proposes an interrupt (similar to the alarm function).

NVRAM (Non-Volatile Detachable RAM) has the feature of power-down without losing data, and can be used to save system setup information, such as network protocol parameters. The previous setup information can still be read after the system is powered down or restarted. Its bit width is 8 bits, which is smaller than the CPU word length. The article deliberately chooses a memory chip that is inconsistent with the CPU word length, creating conditions for the discussion in the next section.

The UART completes the conversion of CPU parallel data transmission and RS-232 serial data transmission. It can send an interrupt to the CPU after receiving [1~MAX_BUFFER] bytes. MAX_BUFFER stores the maximum buffer of the received byte for the UART chip.

The keyboard controller and display controller complete the control of the system man-machine interface.

The above provides a more complete embedded system hardware architecture, the actual system may contain fewer peripherals. The reason why we choose a complete system is to discuss all aspects of embedded system C language programming skills in a more comprehensive way. All the equipment will become the analysis target of the following.

Embedded systems require good software development environment support. Because the target system resources of embedded systems are limited, it is impossible to build a large and complex development environment on them, so the development environment and target operating environment are separated from each other. Therefore, the development method of the embedded application software is generally to establish a development environment on the host (Host), perform application coding and cross-compilation, and then the host establishes a connection with the target (Target), and downloads the application to the target machine. Cross-commissioning, debugging and optimization, and finally the application is solidified to the actual operation of the target machine.

CAD-UL is an embedded application software development environment for x86 processors. It runs on top of the Windows operating system and can generate object code for x86 processors and pass the COM port (RS-232 serial port) or Ethernet of the PC. The port is downloaded to the target machine to run. The monitor program resident in the FLASH memory of the target machine can monitor the user debugging instructions on the host Windows debugging platform, and obtain the value of the CPU register, the storage space of the target machine, and the content of the I/O space.

The following chapters will explain the programming skills of the C language embedded system from the aspects of software architecture, memory operation, screen operation, keyboard operation, performance optimization and so on. Software architecture is a macro concept, and has little connection with specific hardware; memory operation mainly involves FLASH, RAM and NVRAM chips in the system; screen operation involves display controller and real clock; keyboard operation mainly involves keyboard controller; performance optimization Then give some specific techniques to reduce program time and space consumption.

There will be 25 passes in our cultivation journey. These gates are divided into two categories, one is skill type and has strong applicability; the other is common sense type, which has some meaning in theory.

So, let's go.

C language embedded system programming considerations software architecture articles

The "plan" of the module division is the meaning of planning, which means how to reasonably divide a large software into a series of functionally independent parts to complete the system.

Module division

The "plan" of the module division is the meaning of planning, which means how to reasonably divide a large software into a series of functionally independent parts to complete the system. As a structured programming language, C language mainly depends on functions in the division of modules (division according to function becomes an error in object-oriented design, Newton's law encounters relativity), C language modular programming needs to be understood as follows concept:

(1) The module is a combination of a .c file and a .h file. The header file (.h) is a declaration for the interface of the module;

(2) The external functions and data provided by a module to other modules must be declared with the extern keyword in the file in .h;

(3) The functions and global variables in the module must be declared with the staTIc keyword at the beginning of the .c file;

(4) Never define variables in the .h file! The difference between defining a variable and declaring a variable is that the definition creates an operation for memory allocation, which is the concept of the assembly phase; the declaration simply tells the module containing the declaration to look for external functions and variables from other modules during the connection phase. Such as:

/*module1.h*/

Int a = 5; /* defines int a */ in the .h file of module 1.

/*module1 .c*/

#include "module1.h" /* Contains module #'s .h file in module 1*/

/*module2 .c*/

#i nclude “module1.h” /* contains the .h file of module 1 in module 2*/

/*module3 .c*/

#i nclude "module1.h" /* Contains module #'s .h file in module 3*/

The result of the above procedure is that the integer variables a are defined in modules 1, 2, and 3, and a corresponds to different address units in different modules. This kind of program is never needed in the world. The correct way is:

/*module1.h*/

Extern int a; /* declare int a */ in the .h file of module 1.

/*module1 .c*/

#i nclude “module1.h” /* contains module 1.h file* in module 1.

Int a = 5; /* defines int a * in the .c file of module 1.

/*module2 .c*/

#i nclude “module1.h” /* contains the .h file of module 1 in module 2*/

/*module3 .c*/

#i nclude "module1.h" /* Contains module #'s .h file in module 3*/

Thus, if modules 1, 2, and 3 operate a, they correspond to the same memory unit.

An embedded system usually consists of two types of modules:

(1) a hardware driver module, one specific hardware corresponding to one module;

(2) The software function module, the division of the module should meet the requirements of low coupling and high cohesion.

Multitasking or single task

The so-called "single task system" means that the system cannot support multi-task concurrent operations and performs a task in a macroscopic manner. Multitasking systems can perform multiple tasks "simultaneously" in a macroscopic parallel (possibly serially).

Multitasking concurrent execution typically relies on a multitasking operating system (OS). The core of a multitasking OS is the system scheduler, which uses task control blocks (TCBs) to manage task scheduling functions. The TCB includes information such as the current state of the task, priority, events or resources to wait, the start address of the task 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 an ongoing 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 from its TCB and placed in each register.

Typical examples of embedded multitasking OS are Vxworks, ucLinux, and so on. Embedded OS is not an unreachable altar. We can use a less than 1000 lines of code to implement a simpler OS kernel for the 80186 processor. The author is preparing for this work, hoping to contribute to everyone. .

Whether to choose multi-tasking or single-tasking, depends on whether the software system is huge. For example, most mobile phone programs are multi-tasking, but some PHS protocol stacks are single-tasking. Without an operating system, their main programs take turns to call the processing programs of various software modules to simulate a multi-tasking environment.

Single task program typical architecture

(1) Execute from the specified address at the time of CPU reset;

(2) Jump to the assembly code startup to execute;

(3) Jump to the main program of the user main program, complete in main:

a. Initially test each hardware device;

b. Initialize each software module;

c. Enter the infinite loop (infinite loop), call the processing function of each module

The user main program and the processing functions of each module are completed in C language. The user's main program finally enters an infinite loop, and its preferred solution is:

While(1)

{

}

Some programmers write this:

For(;;)

{

}

This grammar does not exactly express the meaning of the code. We can't see anything from for(;;), only to understand that for(;;) means unconditional loop in C language to understand its meaning.

Here are a few "famous" infinite loops:

(1) The operating system is an infinite loop;

(2) WIN32 program is an infinite loop;

(3) Embedded system software is an infinite loop;

(4) The thread processing function of a multithreaded program is an infinite loop.

You may argue and say out loud: "Everything is not absolute. 2, 3, and 4 are not infinite loops." Yes, you are right, but you can't get flowers and applause. In fact, this is a point that doesn't make much sense, because the world never needs a WIN32 program that calls the OS to kill it after processing a few messages. It doesn't need an embedded system that just breaks itself when it starts RUN. You don't need to start somehow to get rid of your own thread. Sometimes it is not convenience but trouble to make too strict. Never seen, the five-layer TCP/IP protocol stack goes beyond the rigorous ISO/OSI seven-layer protocol stack to become the de facto standard?

There are often netizens discussing:

Printf("%d,%d",++i,i++); /* What is the output? */

c = a+++b; /* c=? */

And so on. In the face of these problems, we can only express our heartfelt feelings: 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 to the end of the world.

Interrupt service routine

Interrupts are an important part of an embedded system, but do not include interrupts in Standard C. Many compiler developers have added support for interrupts on standard C, providing new keywords for signing interrupt service routines (ISRs), similar to __interrupt, #program interrupt, and so on. When a function is defined as an ISR, the compiler automatically adds the interrupt on-site stacking and popping code required by the interrupt service routine for the function.

The interrupt service routine needs to meet the following requirements:

(1) cannot return a value;

(2) The parameters cannot be passed to the ISR;

(3) The ISR should be as short as possible;

(4) The printf(char * lpFormatString,...) function introduces reentrancy and performance issues and cannot be used in ISR.

In the development of a project, we designed a queue. In the interrupt service program, we just add the interrupt type to the queue. In the infinite loop of the main program, we continuously scan the interrupt queue for interrupts. The first interrupt type is processed accordingly.

/* Store interrupted queues*/

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 at the end of the queue*/

}

Determine if there is an interruption in the main program loop:

While(1)

{

If( !IsIntQueueEmpty() )

{

intType = GetFirsTInt();

Is switch(intType) /* very similar to the message parsing function of a WIN32 program? */

{

/* Yes, our interrupt type resolution is very similar to message driver*/

Case xxx: /* We call it "interrupt drive"? */

...

Break;

Case xxx:

...

Break;

...

}

}

}

The interrupt service program designed as described above is small, and the actual work is performed by the main program.

The "plan" of the module division is the meaning of planning, which means how to reasonably divide a large software into a series of functionally independent parts to complete the system.

Hardware driver module

A hardware driver module should usually include the following functions:

(1) Interrupt service program ISR

(2) Hardware initialization

a. Modify the register, set the hardware parameters (such as the UART should set its baud rate, AD / DA equipment should set its sampling rate, etc.);

b. Write the interrupt service routine entry address to the interrupt vector table:

/* Set the interrupt vector table */

m_myPtr = make_far_pointer(0l); /* returns a void far pointer void far * */

m_myPtr += ITYPE_UART; /* ITYPE_UART: uart interrupt service routine */

/* Offset from the first address of the interrupt vector table */

*m_myPtr = &UART _Isr; /* UART _Isr: Interrupt Service Routine for UART*/

(3) Set the CPU control line for the hardware

a. If the control line can be used for PIO (programmable I/O) and control signals, set the corresponding register in the CPU 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 operational interface functions for the device. For example, for an LCD, the driver module should provide functions such as drawing pixels, drawing lines, drawing a matrix, and displaying a character dot matrix; for a real clock, the driver module needs to provide functions such as acquisition time and set time.

Object-oriented C

In the object-oriented language, the concept of a class appears. A class is a collection of specific operations on a particular piece of data. A class contains two categories: data and operations. The struct in C is just a collection of data. We can use function pointers to simulate a struct as a "class" containing data and operations. The following C program simulates one of the simplest "classes":

#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 three object-oriented features: encapsulation, inheritance and polymorphism, but more often, we just need to encapsulate data and behavior to solve the problem of software structure confusion. The purpose of C-simulating object-oriented thinking is not to simulate the behavior itself, but to solve the problem that the overall framework structure of the program is scattered, data and functions are disconnected when programming in C language in some cases. We will see examples of this in the following chapters.

to sum up

This article introduces the knowledge of embedded system programming software architecture, including module partitioning, multitasking or single task selection, single task program typical architecture, interrupt service program, hardware driver module design, etc., which gives an embedded macroscopically. The main elements of the system software.

Remember: the software structure is the soul of the software! The confusing procedures are extremely difficult, and debugging, testing, maintenance, and upgrading are extremely difficult.

C language embedded system programming considerations memory operation

In the programming of embedded systems, it is often required to read and write content in a specific memory unit, and assemble corresponding MOV instructions, and the programming languages ​​other than C/C++ have no direct access to absolute addresses.

Data pointer

In the programming of embedded systems, it is often required to read and write content in a specific memory unit, and assemble corresponding MOV instructions, and programming languages ​​other than C/C++ have basically no direct access to absolute addresses. In the actual debugging of the embedded system, the C-language pointer has the ability to read and write the contents of the absolute address unit. Direct manipulation of memory with pointers occurs in the following situations:

(1) An I/O chip is located in the storage space of the CPU instead of the I/O space, and the register corresponds to a specific address;

(2) The two CPUs communicate with each other in a dual port RAM, and the CPU needs to write content in a specific unit (called a mail box) of the dual port RAM to generate an interrupt in the other CPU;

(3) Read Chinese characters and English fonts burned in specific units of ROM or FLASH.

for example:

Unsigned char *p = (unsigned char *)0xF000FF00;

*p=11;

The meaning of the above program is to write 11 at the absolute address 0xF0000 + 0xFF00 (80186 uses a 16-bit segment address and a 16-bit offset address).

When using an absolute address pointer, be aware that the result of the pointer incrementing and decrementing operation depends on the data type pointed to by the pointer. The result of p++ in the above example is p = 0xF000FF01, if p points to int, ie:

Int *p = (int *)0xF000FF00;

The result of p++ (or ++p) is equivalent to: p = p+sizeof(int), and the result of p-(or -p) is p = p-sizeof(int).

Similarly, if executed:

Long int *p = (long int *)0xF000FF00;

Then the result of p++ (or ++p) is equivalent to: p = p+sizeof(long int) , and the result of p-(or -p) is p = p-sizeof(long int).

Remember: the CPU is addressed in bytes, and the C language pointer is incremented and decremented by the length of the data type pointed to. Understanding this is important for manipulating memory directly with pointers.

Function pointer

First understand the following three questions:

(1) The function name in C language directly corresponds to the address of the instruction code generated by the function in memory, so the function name can be directly assigned to the pointer to the function;

(2) The calling function is actually equivalent to "transfer instruction + parameter transfer processing + return position onto the stack". Essentially, the most core operation is to assign the first address of the target code generated by the function to the PC register of the CPU;

(3) Because the essence of the function call is to jump to the code of an address unit to execute, so you can "call" a function entity that does not exist at all, halo? Please look down:

Please take out any of the university's "Microcomputer Principles" textbooks that you can get. The book says that after the 186 CPU starts, it jumps to the absolute address 0xFFFF0 (corresponding to the C language pointer is 0xF000FFF0, 0xF000 is the segment address, 0xFFF0 is the segment Offset) Execution, please see the following code:

Typedef void (*lp) ( ); /* Defines a parameterless, no return type */

/* function pointer type */

Lp lpReset = (lp)0xF000FFF0; /* Define a function pointer to */

/* The position of the first instruction executed after the CPU is started*/

lpReset(); /* Call function */

In the above program, we didn't see any function entity at all, but we executed a function call like this: lpReset(), which actually acts as a "soft restart" and jumps to the first time after the CPU starts. The location of the instruction to be executed.

Remember: the function has no it, only the instruction set ear; you can call a function without a function body, essentially just start an instruction with another address!

Array vs dynamic application

Dynamic memory applications in embedded systems have stricter requirements than general system programming. This is because the memory space of embedded systems is often very limited. Inadvertent memory leaks can quickly lead to system crashes.

So be sure to ensure that your malloc and free pairs appear, if you write a program like this:

Char * (void)

{

Char *p;

p = (char *)malloc(...);

If(p==NULL)

...;

... /* A series of operations for p*/

Return p;

}

Call () somewhere, use the memory after the dynamic application, and then free it, as follows:

Char *q = ();

...

Free(q);

The above code is obviously unreasonable because it violates the principle that malloc and free appear in pairs, that is, the principle of "who applies, who releases it". Failure to satisfy this principle will result in increased code coupling because the user needs to know the internal details when calling the function!

The correct way is to apply for memory at the call and pass in the function as follows:

Char *p=malloc(...);

If(p==NULL)

...;

(p);

...

Free(p);

p=NULL;

The function receives the parameter p as follows:

Void (char *p)

{

... /* A series of operations for p*/

}

Basically, dynamic application memory can be replaced with a larger array. For programming novices, I recommend you try to use arrays! Embedded systems can receive flaws with a broad mind, and cannot be "Haina" errors. After all, Guo Jing, who has worked hard in the most stupid way, has surpassed Yang Kang, who is clever and intelligent, but who is politically wrong and takes the counter-revolutionary path.

Give the principle:

(1) Use arrays as much as possible, and arrays cannot be accessed across borders (the truth is one step beyond the

Solar Cable

Solar Cable,Pv Cable,Pv Solar Cable,Dc Solar Cable

HENAN QIFAN ELECTRIC CO., LTD. , https://www.hnqifancable.com