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 considerationsDifferent 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 articlesThe "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 operationIn 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 delay, and the array is gloriously completes a chaotic embedded system).
(2) If you use the dynamic application, you must judge whether the application is successful after the application, and malloc and free should appear in pairs!
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.
Keyword const
Const means "read only". The function of distinguishing the following code is very important, and it is also an old growth sigh. If you don't know the difference between them, and you have been crawling in the program world for many years, you can only say that this is a sad thing:
Const int a;
Int const a;
Const int *a;
Int * const a;
Int const * a const;
(1) The role of the keyword const is to convey very useful information to those who read your code. For example, adding a const keyword in front of a function's formal parameters means that this parameter will not be modified in the body of the function and is an "input parameter." When there are multiple formal parameters, the caller of the function can clearly distinguish which input parameters and which are the possible output parameters by means of whether there is a const keyword before the parameter.
(2) Proper use of the keyword const allows the compiler to naturally protect parameters that are not desired to be changed, preventing them from being modified by unintentional code, thus reducing the occurrence of bugs.
Const contains a richer meaning in the C++ language, but in the C language only means: "can only read ordinary variables", can be called "variables that cannot be changed" (this statement seems to be very vocal, but The most accurate expression of the essence of const in C language), the constants required in the compilation phase can still only be defined by #define macro! Therefore, the following procedures are illegal in C:
Const int SIZE = 10;
Char a[SIZE]; /* Illegal: Variables cannot be used in the compile phase */
Keyword volaTIle
The C compiler optimizes the code written by the user, such as the following code:
Int a,b,c;
a = inWord(0x100); /*Read the contents of the I/O space 0x100 port and store it in the a variable*/
b = a;
a = inWord (0x100); / * Read the contents of the I/O space 0x100 port again and store it in the a variable */
c = a;
Most likely it is optimized by the compiler to:
Int a,b,c;
a = inWord(0x100); /*Read the contents of the I/O space 0x100 port and store it in the a variable*/
b = a;
c = a;
However, such an optimization result may cause an error. If the contents of the 0/100 port of the I/O space are written by the other program after the first read operation, the content read by the second read operation is different from the first time. The values ​​of b and c should be different. Adding the volaTIle keyword before the definition of the variable a prevents similar optimizations of the compiler. The correct approach is:
Volatile int a;
The volatile variable may be used in the following situations:
(1) The hardware registers of the parallel device (such as the status register, the code in the example belongs to this class);
(2) Non-automatic variables (ie global variables) that are accessed in an interrupt service routine;
(3) Variables shared by several tasks in a multi-threaded application.
CPU word length is inconsistent with memory bit widthAs mentioned in the background article, this paper deliberately chooses a memory chip that is inconsistent with the CPU word length, in order to solve the discussion in this section and solve the problem that the CPU word length and the memory bit width are inconsistent. The word length of the 80186 is 16, and the bit width of the NVRAM is 8. In this case, we need to provide NVRAM with an interface for reading and writing bytes and words, as follows:
Typedef unsigned char BYTE;
Typedef unsigned int WORD;
/* Function: Read bytes in NVRAM
* Parameter: wOffset, the offset of the read position from the NVRAM base address
* Return: the byte value read
*/
Extern BYTE ReadByteNVRAM(WORD wOffset)
{
LPBYTE lpAddr = (BYTE*)(NVRAM + wOffset * 2); /* Why is the offset ×2? */
Return *lpAddr;
}
/* Function: Read the word in NVRAM
* Parameter: wOffset, the offset of the read position from the NVRAM base address
* Return: the word read
*/
Extern WORD ReadWordNVRAM(WORD wOffset)
{
WORD wTmp = 0;
LPBYTE lpAddr;
/* Read high byte */
lpAddr = (BYTE*)(NVRAM + wOffset * 2); /* Why is the offset ×2? */
wTmp += (*lpAddr)*256;
/* Read low byte */
lpAddr = (BYTE*)(NVRAM + (wOffset +1) * 2); /* Why is the offset ×2? */
wTmp += *lpAddr;
Return wTmp;
}
/* Function: Write a byte to NVRAM
*Parameter: wOffset, offset of the write position from the NVRAM base address
* byData, the byte to be written
*/
Extern void WriteByteNVRAM(WORD wOffset, BYTE byData)
{
...
}
/* Function: Write a word to NVRAM*/
*Parameter: wOffset, offset of the write position from the NVRAM base address
* wData, the word to be written
*/
Extern void WriteWordNVRAM(WORD wOffset, WORD wData)
{
...
}
Zigong asks: Why is the Why offset multiplied by 2?
Sub-æ›°: The interconnection between 16-bit 80186 and 8-bit NVRAM can only be connected to A0 by address line A1, and A0 of the CPU itself is not connected to NVRAM. Therefore, the address of NVRAM can only be an even address, so advance each time in units of 0x10!
Zigong asks again: So why is the address line A0 of the 80186 not connected to the A0 of the NVRAM?
Zi Yan: Please see the "Microcomputer Principles" in "The Analects of Science", which tells the story of the saints about computer composition.
to sum upThis article mainly describes the related skills of memory operation in embedded system C programming. Mastering and understanding the relevant knowledge about data pointers, function pointers, dynamic application memory, const and volatile keywords is a basic requirement for an excellent C language programmer. When we have mastered the above techniques, we have learned 99% of the C language, because the essence of the C language is reflected in the memory operation.
The reason why we use C language for programming in embedded systems is 99% because of its powerful memory operation!
If you love programming, please love C language;
If you love C language, please love the pointer;
If you love the pointer, please love the pointer of the pointer!
C language embedded system programming notes screen operation
The problem to be solved now is that the embedded system often does not use a complete Chinese character library, and often only needs to provide a limited number of Chinese characters for the necessary display functions.
Chinese character processing
The problem to be solved now is that the embedded system often does not use a complete Chinese character library, and often only needs to provide a limited number of Chinese characters for the necessary display functions. For example, it is not necessary to provide a function of displaying "email" on the LCD of a microwave oven; a "short message" does not need to be displayed on an LCD of an air conditioner providing a Chinese character display function, and the like. However, a mobile phone and PHS usually need to include a more complete Chinese character library.
If the included Chinese character library is relatively complete, then it is quite simple to calculate the offset of the Chinese character font in the library from the inner code: the Chinese character library is arranged in the order of the location, the previous byte is the area code of the Chinese character, the latter The byte is the bit number of the word. Each zone records 94 Chinese characters, and the bit number is the position of the word in the zone. Therefore, the formula for calculating the specific position of Chinese characters in the Chinese character library is: 94* (area number-1) + bit number-1. The decrease of 1 is because the array starts with 0 and the area code starts with 1. Simply multiply the number of bytes occupied by a Chinese character font, that is: (94* (area number-1) + bit number -1) * A Chinese character font occupies the number of bytes, taking the 16*16 dot matrix font as an example. The calculation formula is: (94 * (area number - 1) + (bit number - 1)) * 32. The 32-byte information from the position in the Chinese character library records the font information of the word.
For systems that contain a more complete Chinese character library, we can calculate the position of the font using the above rules. But what if you only provide a small amount of Chinese characters? For example, tens to hundreds? The best practice is:
Define the macro:
# define EX_FONT_CHAR()
# define EX_FONT_UNICODE_VAL() (),
# define EX_FONT_ANSI_VAL() (),
Define the structure:
Typedef struct _wide_unicode_font16x16
{
WORD ; /* inner code */
BYTE data[32]; /* font dot matrix*/
}Unicode;
#define CHINESE_CHAR_NUM ... /* Number of Chinese characters*/
Array of font storage:
Unicode chinese[CHINESE_CHAR_NUM] =
{
{
EX_FONT_CHAR("业")
EX_FONT_UNICODE_VAL(0x4e1a)
{0x04, 0x40, 0x04, 0x40, 0x04, 0x40, 0x04, 0x44, 0x44, 0x46, 0x24, 0x4c, 0x24, 0x48, 0x14, 0x50, 0x1c, 0x50, 0x14, 0x60, 0x04, 0x40, 0x04, 0x40, 0x04 , 0x44, 0xff, 0xfe, 0x00, 0x00, 0x00, 0x00}
},
{
EX_FONT_CHAR ("Medium")
EX_FONT_UNICODE_VAL(0x4e2d)
{0x01, 0x00, 0x01, 0x00, 0x21, 0x08, 0x3f, 0xfc, 0x21, 0x08, 0x21, 0x08, 0x21, 0x08, 0x21, 0x08, 0x21, 0x08,
0x3f, 0xf8, 0x21, 0x08, 0x01, 0x00, 0x01, 0x00, 0x01, 0x00, 0x01, 0x00, 0x01, 0x00}
},
{
EX_FONT_CHAR ("cloud")
EX_FONT_UNICODE_VAL (0x4e91)
{0x00, 0x00, 0x00, 0x30, 0x3f, 0xf8, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0c, 0xff, 0xfe, 0x03, 0x00, 0x07, 0x00,
0x06, 0x40, 0x0c, 0x20, 0x18, 0x10, 0x31, 0xf8, 0x7f, 0x0c, 0x20, 0x08, 0x00, 0x00}
},
{
EX_FONT_CHAR ("piece")
EX_FONT_UNICODE_VAL(0x4ef6)
{0x10, 0x40, 0x1a, 0x40, 0x13, 0x40, 0x32, 0x40, 0x23, 0xfc, 0x64, 0x40, 0xa4, 0x40, 0x28, 0x40, 0x2f, 0xfe,
0x20, 0x40, 0x20, 0x40, 0x20, 0x40, 0x20, 0x40, 0x20, 0x40, 0x20, 0x40, 0x20, 0x40}
}
}
To display a specific Chinese character, you only need to find the internal code from the array and the same required Chinese character code to get the font. If the preceding Chinese characters are arranged in the order of the inner code size in the array, the binary font search method can be used to find the font of the Chinese character more efficiently.
This is a very effective way to organize a small Chinese character library, which can ensure that the program has a good structure.
System time display
The time of the system can be read from the NVRAM. The system typically reads the current time every second and displays it on the LCD with the second interrupt generated by the NVRAM. There is an efficiency issue with regard to the display of time. Because time has its particularity, it is only one minute change in 60 seconds, and one hour change in 60 minutes. If we re-refresh the time on the screen every time, it will waste a lot of system. time.
A better approach is to store the hours, minutes, and seconds as static variables in the time display function, and update the display only when its content changes.
Extern void DisplayTime(...)
{
Static BYTE byHour,byMinute,bySecond;
BYTE byNewHour, byNewMinute, byNewSecond;
byNewHour = GetSysHour();
byNewMinute = GetSysMinute();
byNewSecond = GetSysSecond();
If(byNewHour!= byHour)
{
... /* shows hours*/
byHour = byNewHour;
}
If(byNewMinute!= byMinute)
{
... /* shows minutes*/
byMinute = byNewMinute;
}
If(byNewSecond!= bySecond)
{
... /* shows seconds*/
bySecond = byNewSecond;
}
}
This example can also be used as a proof of the power of the static keyword in C language. Of course, in the C++ language, static has a more powerful power, which makes some data and functions separate from the "object" and becomes part of the "class". It is this feature that has made countless excellent designs of software.
Animated display
The animation is indifferent, there is no such thing as it, and the still picture goes a lot, and it becomes an animation. As time changes, displaying different still images on the screen is the essence of animation. Therefore, in order to display animation on the LCD of an embedded system, it is necessary to use a timer. A world without hardware or software timers is unimaginable:
(1) 没有定时器,一个æ“ä½œç³»ç»Ÿå°†æ— æ³•è¿›è¡Œæ—¶é—´ç‰‡çš„è½®è½¬ï¼ŒäºŽæ˜¯æ— æ³•è¿›è¡Œå¤šä»»åŠ¡çš„è°ƒåº¦ï¼ŒäºŽæ˜¯ä¾¿ä¸å†æˆå…¶ä¸ºä¸€ä¸ªå¤šä»»åŠ¡æ“作系统;
(2) 没有定时器,一个多媒体æ’æ”¾è½¯ä»¶å°†æ— æ³•è¿ä½œï¼Œå› 为它ä¸çŸ¥é“何时应该切æ¢åˆ°ä¸‹ä¸€å¸§ç”»é¢ï¼›
(3) 没有定时器,一个网络åè®®å°†æ— æ³•è¿è½¬ï¼Œå› ä¸ºå…¶æ— æ³•èŽ·çŸ¥ä½•æ—¶åŒ…ä¼ è¾“è¶…æ—¶å¹¶é‡ä¼ ä¹‹ï¼Œæ— æ³•åœ¨ç‰¹å®šçš„æ—¶é—´å®Œæˆç‰¹å®šçš„任务。
å› æ¤ï¼Œæ²¡æœ‰å®šæ—¶å™¨å°†æ„味ç€æ²¡æœ‰æ“作系统ã€æ²¡æœ‰ç½‘络ã€æ²¡æœ‰å¤šåª’ä½“ï¼Œè¿™å°†æ˜¯æ€Žæ ·çš„é»‘æš—ï¼Ÿæ‰€ä»¥ï¼Œåˆç†å¹¶çµæ´»åœ°ä½¿ç”¨å„ç§å®šæ—¶å™¨ï¼Œæ˜¯å¯¹ä¸€ä¸ªè½¯ä»¶äººçš„最基本需求ï¼
在80186为主芯片的嵌入å¼ç³»ç»Ÿä¸ï¼Œæˆ‘们需è¦å€ŸåŠ©ç¡¬ä»¶å®šæ—¶å™¨çš„ä¸æ–æ¥ä½œä¸ºè½¯ä»¶å®šæ—¶å™¨ï¼Œåœ¨ä¸æ–å‘生åŽå˜æ›´ç”»é¢çš„显示内容。在时间显示“xx:xxâ€ä¸è®©å†’å·äº¤æ›¿æœ‰æ— ,æ¯æ¬¡ç§’ä¸æ–å‘生åŽï¼Œéœ€è°ƒç”¨ShowDot:
void ShowDot()
{
static BOOL bShowDot = TRUE; /* å†ä¸€æ¬¡é¢†ç•¥static关键å—çš„å¨åŠ›*/
if(bShowDot)
{
showChar(':',xPos,yPos);
}
Else
{
showChar(' ',xPos,yPos);
}
bShowDot = ï¼ bShowDot;
}
èœå•æ“作
æ— æ•°äººä¸ºä¹‹ç»žå°½è„‘æ±çš„问题终于出现了,在这一节里,我们将看到,在Cè¯è¨€ä¸å“ªæ€•ç”¨åˆ°ä¸€ä¸ç‚¹çš„é¢å‘对象æ€æƒ³ï¼Œè½¯ä»¶ç»“构将会有何ç‰çš„改观ï¼
è¦æ±‚以键盘上的“↠→â€é”®åˆ‡æ¢èœå•ç„¦ç‚¹ï¼Œå½“用户在焦点处于æŸèœå•æ—¶ï¼Œè‹¥æ•²å‡»é”®ç›˜ä¸Šçš„OKã€CANCEL键则调用该焦点èœå•å¯¹åº”之处ç†å‡½æ•°ã€‚我曾ç»å‚»å‚»åœ°è¿™æ ·åšç€ï¼š
/* 按下OK键*/
void onOkKey()
{
/* 判æ–在什么焦点èœå•ä¸ŠæŒ‰ä¸‹Ok键,调用相应处ç†å‡½æ•°*/
Switch(currentFocus)
{
case MENU1:
menu1OnOk();
Break;
case MENU2:
menu2OnOk();
Break;
...
}
}
/* 按下Cancel键*/
void onCancelKey()
{
/* 判æ–在什么焦点èœå•ä¸ŠæŒ‰ä¸‹Cancel键,调用相应处ç†å‡½æ•°*/
Switch(currentFocus)
{
case MENU1:
menu1OnCancel();
Break;
case MENU2:
menu2OnCancel();
Break;
...
}
}
ç»ˆäºŽæœ‰ä¸€å¤©ï¼Œæˆ‘è¿™æ ·åšäº†ï¼š
/* å°†èœå•çš„属性和æ“作“å°è£…â€åœ¨ä¸€èµ·*/
typedef struct tagSysMenu
{
char *text; /* èœå•çš„文本*/
BYTE xPos; /* èœå•åœ¨LCD上的xåæ ‡*/
BYTE yPos; /* èœå•åœ¨LCD上的yåæ ‡*/
void (*onOkFun)(); /* 在该èœå•ä¸ŠæŒ‰ä¸‹ok键的处ç†å‡½æ•°æŒ‡é’ˆ*/
void (*onCancelFun)(); /* 在该èœå•ä¸ŠæŒ‰ä¸‹cancel键的处ç†å‡½æ•°æŒ‡é’ˆ*/
}SysMenu, *LPSysMenu;
当我定义èœå•æ—¶ï¼Œåªéœ€è¦è¿™æ ·ï¼š
static SysMenu menuï¼»MENU_NUMï¼½ =
{
{
“menu1â€ï¼Œ 0, 48, menu1OnOk, menu1OnCancel
}
,
{
“ menu2â€ï¼Œ 7, 48, menu2OnOk, menu2OnCancel
}
,
{
“ menu3â€ï¼Œ 7, 48, menu3OnOk, menu3OnCancel
}
,
{
“ menu4â€ï¼Œ 7, 48, menu4OnOk, menu4OnCancel
}
...
};
OK键和CANCEL键的处ç†å˜æˆï¼š
/* 按下OK键*/
void onOkKey()
{
menu[currentFocusMenu].onOkFun();
}
/* 按下Cancel键*/
void onCancelKey()
{
menu[currentFocusMenu].onCancelFun();
}
程åºè¢«å¤§å¤§ç®€åŒ–了,也开始具有很好的å¯æ‰©å±•æ€§ï¼æˆ‘们仅仅利用了é¢å‘对象ä¸çš„å°è£…æ€æƒ³ï¼Œå°±è®©ç¨‹åºç»“æž„æ¸…æ™°ï¼Œå…¶ç»“æžœæ˜¯å‡ ä¹Žå¯ä»¥åœ¨æ— 需修改程åºçš„情况下在系统ä¸æ·»åŠ 更多的èœå•ï¼Œè€Œç³»ç»Ÿçš„按键处ç†å‡½æ•°ä¿æŒä¸å˜ã€‚
é¢å‘对象,真神了ï¼
模拟MessageBox函数
MessageBox函数,这个Windows编程ä¸çš„超级猛料,ä¸çŸ¥é“是多少入门者第一次用到的函数。还记得我们第一次在Windowsä¸åˆ©ç”¨MessageBox输出“Hello,Worldï¼â€å¯¹è¯æ¡†æ—¶æ–°å¥‡çš„感觉å—ï¼Ÿæ— æ³•ç»Ÿè®¡ï¼Œè¿™ä¸ªä¸–ç•Œä¸Šç©¶ç«Ÿæœ‰å¤šå°‘ç¨‹åºå‘˜å¦ä¹ Windows编程是从MessageBox(“Hello,Worldï¼â€ï¼Œâ€¦ï¼‰å¼€å§‹çš„。在我本科的å¦æ ¡ï¼Œå¹¿æ³›æµä¼ ç€ä¸€ä¸ªè¯æ±‡ï¼Œå«åšâ€œ'Hello,World'级程åºå‘˜â€ï¼Œæ„指入门级程åºå‘˜ï¼Œä½†ä¼¼ä¹Žâ€œ'Hello,World'级â€è¿™ä¸ªè¯´æ³•æ›´æžç¬‘而形象。
嵌入å¼ç³»ç»Ÿä¸æ²¡æœ‰ç»™æˆ‘们æä¾›MessageBox,但是鉴于其功能强大,我们需è¦æ¨¡æ‹Ÿä¹‹ï¼Œä¸€ä¸ªæ¨¡æ‹Ÿçš„MessageBox函数为:
/******************************************
/* 函数å称: MessageBox
/* 功能说明: 弹出å¼å¯¹è¯æ¡†ï¼Œæ˜¾ç¤ºæ醒用户的信æ¯
/* å‚数说明: lpStr --- æ醒用户的å—符串输出信æ¯
/* TYPE --- è¾“å‡ºæ ¼å¼ï¼ˆID_OK = 0, ID_OKCANCEL = 1)
/* 返回值: 返回对è¯æ¡†æŽ¥æ”¶çš„键值,åªæœ‰ä¸¤ç§KEY_OK, KEY_CANCEL
/******************************************
typedef enum TYPE { ID_OK,ID_OKCANCEL }MSG_TYPE;
extern BYTE MessageBox(LPBYTE lpStr, BYTE TYPE)
{
BYTE key = -1;
ClearScreen(); /* 清除å±å¹•*/
DisplayString(xPos,yPos,lpStr,TRUE); /* 显示å—符串*/
/* æ ¹æ®å¯¹è¯æ¡†ç±»åž‹å†³å®šæ˜¯å¦æ˜¾ç¤ºç¡®å®šã€å–消*/
switch (TYPE)
{
case ID_OK:
DisplayString(13,yPos+High+1, “ 确定â€ï¼Œ 0);
Break;
case ID_OKCANCEL:
DisplayString(8, yPos+High+1, “ 确定â€ï¼Œ 0);
DisplayString(17,yPos+High+1, “ å–消â€ï¼Œ 0);
Break;
Default:
Break;
}
DrawRect(0, 0, 239, yPos+High+16+4); /* 绘制外框*/
/* MessageBox是模å¼å¯¹è¯æ¡†ï¼Œé˜»å¡žè¿è¡Œï¼Œç‰å¾…按键*/
while( (key ï¼= KEY_OK) || (key ï¼= KEY_CANCEL) )
{
key = getSysKey();
}
/* 返回按键类型*/
if(key== KEY_OK)
{
return ID_OK;
}
Else
{
return ID_CANCEL;
}
}
ä¸Šè¿°å‡½æ•°ä¸Žæˆ‘ä»¬å¹³ç´ åœ¨VC++ç‰ä¸ä½¿ç”¨çš„MessageBox是何ç‰çš„ç¥žä¼¼å•Šï¼Ÿå®žçŽ°è¿™ä¸ªå‡½æ•°ï¼Œä½ ä¼šçœ‹åˆ°å®ƒåœ¨åµŒå…¥å¼ç³»ç»Ÿä¸çš„å¦™ç”¨æ˜¯æ— ç©·çš„ã€‚
to sum up
æœ¬ç¯‡æ˜¯æœ¬ç³»åˆ—æ–‡ç« ä¸æŠ€å·§æ€§æœ€æ·±çš„一篇,它æ供了嵌入å¼ç³»ç»Ÿå±å¹•æ˜¾ç¤ºæ–¹é¢ä¸€äº›å¾ˆå·§å¦™çš„处ç†æ–¹æ³•ï¼Œçµæ´»ä½¿ç”¨å®ƒä»¬ï¼Œæˆ‘们将ä¸å†è¢«LCD上凌乱ä¸å ªçš„显示内容所困扰。
å±å¹•ä¹ƒåµŒå…¥å¼ç³»ç»Ÿç”Ÿå˜ä¹‹é‡è¦è¾…助,é¢ç›®å¯æ†Žä¹‹æ˜¾ç¤ºå°†å¦ç”¨æˆ·é€ƒä¹‹å¤å¤ã€‚å±å¹•ç¼–程若处ç†ä¸å¥½ï¼Œå°†æ˜¯è½¯ä»¶ä¸æœ€ä¸ç³»ç»Ÿã€æœ€æ··ä¹±çš„部分,笔者曾深å—其害。
Cè¯è¨€åµŒå…¥å¼ç³»ç»Ÿç¼–程注æ„事项之键盘æ“作
处ç†åŠŸèƒ½é”®
让我们æ¥çœ‹çœ‹WIN32编程ä¸ç”¨åˆ°çš„“窗å£â€æ¦‚念,当消æ¯ï¼ˆmessage)被å‘é€ç»™ä¸åŒçª—å£çš„时候,该窗å£çš„消æ¯å¤„ç†å‡½æ•°ï¼ˆæ˜¯ä¸€ä¸ªcallback函数)最终被调用,而在该窗å£çš„消æ¯å¤„ç†å‡½æ•°ä¸ï¼Œåˆæ ¹æ®æ¶ˆæ¯çš„类型调用了该窗å£ä¸çš„对应处ç†å‡½æ•°ã€‚通过这ç§æ–¹å¼ï¼ŒWIN32有效的组织了ä¸åŒçš„窗å£ï¼Œå¹¶å¤„ç†ä¸åŒçª—å£æƒ…况下的消æ¯ã€‚
我们从ä¸å¦ä¹ 到的就是:
(1)将ä¸åŒçš„ç”»é¢ç±»æ¯”为WIN32ä¸ä¸åŒçš„窗å£ï¼Œå°†çª—å£ä¸çš„å„ç§å…ƒç´ (èœå•ã€æŒ‰é’®ç‰ï¼‰åŒ…å«åœ¨çª—å£ä¹‹ä¸ï¼›
(2)给å„个画é¢æ供一个功能键“消æ¯â€å¤„ç†å‡½æ•°ï¼Œè¯¥å‡½æ•°æŽ¥æ”¶æŒ‰é”®ä¿¡æ¯ä¸ºå‚æ•°ï¼›
(3)在å„ç”»é¢çš„功能键“消æ¯â€å¤„ç†å‡½æ•°ä¸ï¼Œåˆ¤æ–按键类型和当å‰ç„¦ç‚¹å…ƒç´ ï¼Œå¹¶è°ƒç”¨å¯¹åº”å…ƒç´ çš„æŒ‰é”®å¤„ç†å‡½æ•°ã€‚
/* 将窗å£å…ƒç´ ã€æ¶ˆæ¯å¤„ç†å‡½æ•°å°è£…在窗å£ä¸*/
struct windows
{
BYTE currentFocus;
ELEMENT elementï¼»ELEMENT_NUMï¼½;
void (*messageFun) (BYTE key);
...
};
/* 消æ¯å¤„ç†å‡½æ•°*/
void message(BYTE key)
{
BYTE i = 0;
/* èŽ·å¾—ç„¦ç‚¹å…ƒç´ */
while ( (element .IDï¼= currentFocus)&& (i 《 ELEMENT_NUM) )
{
i++;
}
/* “消æ¯æ˜ 射†*/
if(i 《 ELEMENT_NUM)
{
Switch(key)
{
case OK:
element.OnOk();
Break;
...
}
}
}
在窗å£çš„消æ¯å¤„ç†å‡½æ•°ä¸è°ƒç”¨ç›¸åº”å…ƒç´ æŒ‰é”®å‡½æ•°çš„è¿‡ç¨‹ç±»ä¼¼äºŽâ€œæ¶ˆæ¯æ˜ å°„â€ï¼Œè¿™æ˜¯æˆ‘们从WIN32编程ä¸å¦ä¹ 到的。编程到了一个境界,很多东西都是相通的了。其它地方的æ€æƒ³å¯ä»¥æ‹¿è¿‡æ¥ä¸ºæˆ‘所用,是为编程ä¸çš„“拿æ¥ä¸»ä¹‰â€ã€‚
在这个例åä¸ï¼Œå¦‚果我们还想玩得更大一点,我们å¯ä»¥å€Ÿé‰´MFCä¸å¤„ç†MESSAGE_MAP的方法,我们也å¯ä»¥å¦ä¹ MFCå®šä¹‰å‡ ä¸ªç²¾å¦™çš„å®æ¥å®žçŽ°â€œæ¶ˆæ¯æ˜ å°„â€ã€‚
处ç†æ•°å—é”®
用户输入数å—时是一ä½ä¸€ä½è¾“入的,æ¯ä¸€ä½çš„输入都对应ç€å±å¹•ä¸Šçš„一个显示ä½ç½®ï¼ˆxåæ ‡ï¼Œyåæ ‡ï¼‰ã€‚æ¤å¤–,程åºè¿˜éœ€è¦è®°å½•è¯¥ä½ç½®è¾“入的值,所以有效组织用户数å—输入的最佳方å¼æ˜¯å®šä¹‰ä¸€ä¸ªç»“构体,将åæ ‡å’Œæ•°å€¼æ†ç»‘在一起:
/* 用户数å—输入结构体*/
typedef struct tagInputNum
{
BYTE byNum; /* 接收用户输入赋值*/
BYTE xPos; /* æ•°å—输入在å±å¹•ä¸Šçš„显示ä½ç½®xåæ ‡*/
BYTE yPos; /* æ•°å—输入在å±å¹•ä¸Šçš„显示ä½ç½®yåæ ‡*/
}InputNum, *LPInputNum;
那么接收用户输入就å¯ä»¥å®šä¹‰ä¸€ä¸ªç»“构体数组,用数组ä¸çš„å„ä½ç»„æˆä¸€ä¸ªå®Œæ•´çš„æ•°å—:
InputNum inputElementï¼»NUM_LENGTHï¼½; /* 接收用户数å—输入的数组*/
/* æ•°å—按键处ç†å‡½æ•°*/
extern void onNumKey(BYTE num)
{
if(num==0|| num==1) /* åªæŽ¥æ”¶äºŒè¿›åˆ¶è¾“å…¥*/
{
/* 在å±å¹•ä¸Šæ˜¾ç¤ºç”¨æˆ·è¾“å…¥*/
DrawText(inputElementï¼»currentElementInputPlaceï¼½.xPos, inputElementï¼»currentElementInputPlaceï¼½.yPos, “%1dâ€ï¼Œ num);
/* å°†è¾“å…¥èµ‹å€¼ç»™æ•°ç»„å…ƒç´ */
inputElementï¼»currentElementInputPlaceï¼½.byNum = num;
/* 焦点åŠå…‰æ ‡å³ç§»*/
moveToRight();
}
}
将数å—æ¯ä¸€ä½è¾“入的åæ ‡å’Œè¾“å…¥å€¼æ†ç»‘åŽï¼Œåœ¨æ•°å—键处ç†å‡½æ•°ä¸å°±å¯ä»¥è¾ƒæœ‰ç»“构的组织程åºï¼Œä½¿ç¨‹åºæ˜¾å¾—很紧凑。
æ•´ç†ç”¨æˆ·è¾“å…¥
继ç»ç¬¬2节的例å,在第2节的onNumKey函数ä¸ï¼Œåªæ˜¯èŽ·å–了数å—çš„æ¯ä¸€ä½ï¼Œå› 而我们需è¦å°†å…¶è½¬åŒ–为有效数æ®ï¼Œè¬å¦‚è¦è½¬åŒ–为有效的XXXæ•°æ®ï¼Œå…¶æ–¹æ³•æ˜¯ï¼š
/* 从2进制数æ®ä½è½¬åŒ–为有效数æ®ï¼šXXX */
void convertToXXX()
{
BYTE i;
XXX = 0;
for (i = 0; i 《 NUM_LENGTH; i++)
{
XXX += inputElement.byNum*power(2, NUM_LENGTH - i - 1);
}
}
å之,我们也å¯èƒ½éœ€è¦åœ¨å±å¹•ä¸Šæ˜¾ç¤ºé‚£äº›æœ‰æ•ˆçš„æ•°æ®ä½ï¼Œå› 为我们也需è¦èƒ½å¤Ÿåå‘转化:
/* 从有效数æ®è½¬åŒ–为2进制数æ®ä½ï¼šXXX */
void convertFromXXX()
{
BYTE i;
XXX = 0;
for (i = 0; i 《 NUM_LENGTH; i++)
{
inputElement.byNum = XXX / power(2, NUM_LENGTH - i - 1) % 2;
}
}
当然在上é¢çš„例åä¸ï¼Œå› 为数æ®æ˜¯2进制的,用power函数ä¸æ˜¯å¾ˆå¥½çš„选择,直接用“《《 》》â€ç§»ä½æ“作效率更高,我们仅是为了说明问题的方便。试想,如果用户输入是å进制的,power函数或许是唯一的选择了。
to sum up
本篇给出了键盘æ“作所涉åŠçš„å„个方é¢ï¼šåŠŸèƒ½é”®å¤„ç†ã€æ•°å—键处ç†åŠç”¨æˆ·è¾“入整ç†ï¼ŒåŸºæœ¬ä¸Šæ供了一个全套的按键处ç†æ–¹æ¡ˆã€‚对于功能键处ç†æ–¹æ³•ï¼Œå°†LCDå±å¹•ä¸ŽWindows窗å£è¿›è¡Œç±»æ¯”,æ出了较新颖地解决å±å¹•ã€é”®ç›˜ç¹æ‚交互问题的方案。
计算机å¦çš„è®¸å¤šçŸ¥è¯†éƒ½å…·æœ‰ç›¸é€šæ€§ï¼Œå› è€Œï¼Œä¸æ–追赶时髦技术而忽略基本功的åšæ³•æ˜¯å¾’åŠ³æ— æ„的。我们最多需è¦â€œç²¾é€šâ€ä¸‰ç§è¯è¨€ï¼ˆç²¾é€šï¼Œä¸€ä¸ªåœ¨å¦‚今的求èŒç®€åŽ†é‡Œæ³›æ»¥æˆç¾çš„è¯è¯ï¼‰ï¼Œæœ€ä½³æ‹æ¡£æ˜¯æ±‡ç¼–ã€Cã€C++(或JAVAï¼‰ï¼Œå¾ˆæ˜¾ç„¶ï¼Œå¦‚æžœä½ â€œç²¾é€šâ€äº†è¿™ä¸‰ç§è¯è¨€ï¼Œå…¶å®ƒè¯è¨€ä½ 应该是å¯ä»¥å¾ˆå¿«â€œç†Ÿæ‚‰â€çš„,å¦åˆ™ä½ 就没有“精通â€å®ƒä»¬ã€‚
Cè¯è¨€åµŒå…¥å¼ç³»ç»Ÿç¼–程注æ„事项之性能优化
在Cè¯è¨€ä¸ï¼Œå®æ˜¯äº§ç”Ÿå†…嵌代ç 的唯一方法。对于嵌入å¼ç³»ç»Ÿè€Œè¨€ï¼Œä¸ºäº†èƒ½è¾¾åˆ°æ€§èƒ½è¦æ±‚,å®æ˜¯ä¸€ç§å¾ˆå¥½çš„代替函数的方法
使用å®å®šä¹‰
在Cè¯è¨€ä¸ï¼Œå®æ˜¯äº§ç”Ÿå†…嵌代ç 的唯一方法。对于嵌入å¼ç³»ç»Ÿè€Œè¨€ï¼Œä¸ºäº†èƒ½è¾¾åˆ°æ€§èƒ½è¦æ±‚,å®æ˜¯ä¸€ç§å¾ˆå¥½çš„代替函数的方法。
å†™ä¸€ä¸ªâ€œæ ‡å‡†â€å®MIN ,这个å®è¾“入两个å‚数并返回较å°çš„一个:
错误åšæ³•ï¼š
#define MIN(A,B) ( A 《= B ? A : B )
æ£ç¡®åšæ³•ï¼š
#define MIN(A,B) ((A)《= (B) ? (A) : (B) )
对于å®ï¼Œæˆ‘们需è¦çŸ¥é“三点:
(1)å®å®šä¹‰â€œåƒâ€å‡½æ•°ï¼›
(2)å®å®šä¹‰ä¸æ˜¯å‡½æ•°ï¼Œå› 而需è¦æ‹¬ä¸Šæ‰€æœ‰â€œå‚æ•°â€ï¼›
(3)å®å®šä¹‰å¯èƒ½äº§ç”Ÿå‰¯ä½œç”¨ã€‚
下é¢çš„代ç :
least = MIN(*p++, b);
将被替æ¢ä¸ºï¼š
( (*p++) 《= (b) ?(*p++):(b) )
å‘ç”Ÿçš„äº‹æƒ…æ— æ³•é¢„æ–™ã€‚
å› è€Œä¸è¦ç»™å®å®šä¹‰ä¼ 入有副作用的“å‚æ•°â€ã€‚
使用寄å˜å™¨å˜é‡
当对一个å˜é‡é¢‘ç¹è¢«è¯»å†™æ—¶ï¼Œéœ€è¦åå¤è®¿é—®å†…å˜ï¼Œä»Žè€ŒèŠ±è´¹å¤§é‡çš„å˜å–时间。为æ¤ï¼ŒCè¯è¨€æ供了一ç§å˜é‡ï¼Œå³å¯„å˜å™¨å˜é‡ã€‚è¿™ç§å˜é‡å˜æ”¾åœ¨CPU的寄å˜å™¨ä¸ï¼Œä½¿ç”¨æ—¶ï¼Œä¸éœ€è¦è®¿é—®å†…å˜ï¼Œè€Œç›´æŽ¥ä»Žå¯„å˜å™¨ä¸è¯»å†™ï¼Œä»Žè€Œæ高效率。寄å˜å™¨å˜é‡çš„说明符是register。对于循环次数较多的循环控制å˜é‡åŠå¾ªçŽ¯ä½“内åå¤ä½¿ç”¨çš„å˜é‡å‡å¯å®šä¹‰ä¸ºå¯„å˜å™¨å˜é‡ï¼Œè€Œå¾ªçŽ¯è®¡æ•°æ˜¯åº”用寄å˜å™¨å˜é‡çš„最好候选者。
(1) åªæœ‰å±€éƒ¨è‡ªåŠ¨å˜é‡å’Œå½¢å‚æ‰å¯ä»¥å®šä¹‰ä¸ºå¯„å˜å™¨å˜é‡ã€‚å› ä¸ºå¯„å˜å™¨å˜é‡å±žäºŽåŠ¨æ€å˜å‚¨æ–¹å¼ï¼Œå‡¡éœ€è¦é‡‡ç”¨é™æ€å˜å‚¨æ–¹å¼çš„é‡éƒ½ä¸èƒ½å®šä¹‰ä¸ºå¯„å˜å™¨å˜é‡ï¼ŒåŒ…括:模å—间全局å˜é‡ã€æ¨¡å—内全局å˜é‡ã€å±€éƒ¨staticå˜é‡ï¼›
(2) register是一个“建议â€åž‹å…³é”®å—,æ„指程åºå»ºè®®è¯¥å˜é‡æ”¾åœ¨å¯„å˜å™¨ä¸ï¼Œä½†æœ€ç»ˆè¯¥å˜é‡å¯èƒ½å› 为æ¡ä»¶ä¸æ»¡è¶³å¹¶æœªæˆä¸ºå¯„å˜å™¨å˜é‡ï¼Œè€Œæ˜¯è¢«æ”¾åœ¨äº†å˜å‚¨å™¨ä¸ï¼Œä½†ç¼–译器ä¸å¹¶ä¸æŠ¥é”™ï¼ˆåœ¨C++è¯è¨€ä¸æœ‰å¦ä¸€ä¸ªâ€œå»ºè®®â€åž‹å…³é”®å—:inline)。
下é¢æ˜¯ä¸€ä¸ªé‡‡ç”¨å¯„å˜å™¨å˜é‡çš„例å:
/* 求1+2+3+….+n的值*/
WORD Addition(BYTE n)
{
register i,s=0;
for(i=1;i《=n;i++)
{
s=s+i;
}
Return s;
}
本程åºå¾ªçŽ¯n次,iå’Œs都被频ç¹ä½¿ç”¨ï¼Œå› æ¤å¯å®šä¹‰ä¸ºå¯„å˜å™¨å˜é‡ã€‚
内嵌汇编
程åºä¸å¯¹æ—¶é—´è¦æ±‚苛刻的部分å¯ä»¥ç”¨å†…嵌汇编æ¥é‡å†™ï¼Œä»¥å¸¦æ¥é€Ÿåº¦ä¸Šçš„显著æ高。但是,开å‘和测试汇编代ç æ˜¯ä¸€ä»¶è¾›è‹¦çš„å·¥ä½œï¼Œå®ƒå°†èŠ±è´¹æ›´é•¿çš„æ—¶é—´ï¼Œå› è€Œè¦æ…Žé‡é€‰æ‹©è¦ç”¨æ±‡ç¼–的部分。
在程åºä¸ï¼Œå˜åœ¨ä¸€ä¸ª80-20原则,å³20%的程åºæ¶ˆè€—了80%çš„è¿è¡Œæ—¶é—´ï¼Œå› 而我们è¦æ”¹è¿›æ•ˆçŽ‡ï¼Œæœ€ä¸»è¦æ˜¯è€ƒè™‘改进那20%的代ç 。
嵌入å¼C程åºä¸ä¸»è¦ä½¿ç”¨åœ¨çº¿æ±‡ç¼–,å³åœ¨C程åºä¸ç›´æŽ¥æ’å…¥_asm{ }内嵌汇编è¯å¥ï¼š
/* 把两个输入å‚æ•°çš„å€¼ç›¸åŠ ï¼Œç»“æžœå˜æ”¾åˆ°å¦å¤–一个全局å˜é‡ä¸*/
int result;
void Add(long a, long *b)
{
_asm
{
MOV AX, a
MOV BX, b
ADD AX, [BX]
MOV result, AX
}
}
利用硬件特性
首先è¦æ˜Žç™½CPU对å„ç§å˜å‚¨å™¨çš„访问速度,基本上是:
CPU内部RAM》外部åŒæ¥RAM》外部异æ¥RAM》FLASH/ROM
对于程åºä»£ç ,已ç»è¢«çƒ§å½•åœ¨FLASH或ROMä¸ï¼Œæˆ‘们å¯ä»¥è®©CPU直接从其ä¸è¯»å–代ç 执行,但通常这ä¸æ˜¯ä¸€ä¸ªå¥½åŠžæ³•ï¼Œæˆ‘们最好在系统å¯åŠ¨åŽå°†FLASH或ROMä¸çš„ç›®æ ‡ä»£ç æ‹·è´å…¥RAMä¸åŽå†æ‰§è¡Œä»¥æ高å–指令速度;
对于UARTç‰è®¾å¤‡ï¼Œå…¶å†…部有一定容é‡çš„接收BUFFER,我们应尽é‡åœ¨BUFFER被å 满åŽå†å‘CPUæ出ä¸æ–。例如计算机终端在å‘ç›®æ ‡æœºé€šè¿‡RS-232ä¼ é€’æ•°æ®æ—¶ï¼Œä¸å®œè®¾ç½®UARTåªæŽ¥æ”¶åˆ°ä¸€ä¸ªBYTEå°±å‘CPUæä¸æ–ï¼Œä»Žè€Œæ— è°“æµªè´¹ä¸æ–处ç†æ—¶é—´ï¼›
如果对æŸè®¾å¤‡èƒ½é‡‡å–DMAæ–¹å¼è¯»å–,就采用DMA读å–,DMA读å–æ–¹å¼åœ¨è¯»å–ç›®æ ‡ä¸åŒ…å«çš„å˜å‚¨ä¿¡æ¯è¾ƒå¤§æ—¶æ•ˆçŽ‡è¾ƒé«˜ï¼Œå…¶æ•°æ®ä¼ 输的基本å•ä½æ˜¯å—ï¼Œè€Œæ‰€ä¼ è¾“çš„æ•°æ®æ˜¯ä»Žè®¾å¤‡ç›´æŽ¥é€å…¥å†…å˜çš„(或者相å)。DMAæ–¹å¼è¾ƒä¹‹ä¸æ–驱动方å¼ï¼Œå‡å°‘了CPU 对外设的干预,进一æ¥æ高了CPU与外设的并行æ“作程度。
活用ä½æ“作
使用Cè¯è¨€çš„ä½æ“作å¯ä»¥å‡å°‘除法和å–模的è¿ç®—。在计算机程åºä¸æ•°æ®çš„ä½æ˜¯å¯ä»¥æ“作的最å°æ•°æ®å•ä½ï¼Œç†è®ºä¸Šå¯ä»¥ç”¨â€œä½è¿ç®—â€æ¥å®Œæˆæ‰€æœ‰çš„è¿ç®—å’Œæ“ä½œï¼Œå› è€Œï¼Œçµæ´»çš„ä½æ“作å¯ä»¥æœ‰æ•ˆåœ°æ高程åºè¿è¡Œçš„效率。 Examples are as follows:
/* 方法1 */
Int i,j;
i = 879 / 16;
j = 562 % 32;
/* 方法2 */
Int i,j;
i = 879 》》 4;
j = 562 - (562 》》 5 《《 5);
对于以2的指数次方为“*â€ã€â€œ/â€æˆ–“%â€å› åçš„æ•°å¦è¿ç®—,转化为移ä½è¿ç®—“《《 》》â€é€šå¸¸å¯ä»¥æé«˜ç®—æ³•æ•ˆçŽ‡ã€‚å› ä¸ºä¹˜é™¤è¿ç®—指令周期通常比移ä½è¿ç®—大。
Cè¯è¨€ä½è¿ç®—除了å¯ä»¥æ高è¿ç®—效率外,在嵌入å¼ç³»ç»Ÿçš„编程ä¸ï¼Œå®ƒçš„å¦ä¸€ä¸ªæœ€å…¸åž‹çš„应用,而且å分广泛地æ£åœ¨è¢«ä½¿ç”¨ç€çš„是ä½é—´çš„与(&)ã€æˆ–(|)ã€éžï¼ˆ~)æ“作,这跟嵌入å¼ç³»ç»Ÿçš„编程特点有很大关系。我们通常è¦å¯¹ç¡¬ä»¶å¯„å˜å™¨è¿›è¡Œä½è®¾ç½®ï¼Œè¬å¦‚,我们通过将AM186ERåž‹80186处ç†å™¨çš„ä¸æ–å±è”½æŽ§åˆ¶å¯„å˜å™¨çš„第低6ä½è®¾ç½®ä¸º0(开ä¸æ–2),最通用的åšæ³•æ˜¯ï¼š
#define INT_I2_MASK 0x0040
wTemp = inword(INT_MASK);
outword(INT_MASK, wTemp &~INT_I2_MASK);
而将该ä½è®¾ç½®ä¸º1çš„åšæ³•æ˜¯ï¼š
#define INT_I2_MASK 0x0040
wTemp = inword(INT_MASK);
outword(INT_MASK, wTemp | INT_I2_MASK);
判æ–该ä½æ˜¯å¦ä¸º1çš„åšæ³•æ˜¯ï¼š
#define INT_I2_MASK 0x0040
wTemp = inword(INT_MASK);
if(wTemp & INT_I2_MASK)
{
… /* 该ä½ä¸º1 */
}
上述方法在嵌入å¼ç³»ç»Ÿçš„编程ä¸æ˜¯éžå¸¸å¸¸è§çš„,我们需è¦ç‰¢å›ºæŽŒæ¡ã€‚
to sum up
在性能优化方é¢æ°¸è¿œæ³¨æ„80-20准备,ä¸è¦ä¼˜åŒ–程åºä¸å¼€é”€ä¸å¤§çš„é‚£80%ï¼Œè¿™æ˜¯åŠ³è€Œæ— åŠŸçš„ã€‚
å®å®šä¹‰æ˜¯Cè¯è¨€ä¸å®žçŽ°ç±»ä¼¼å‡½æ•°åŠŸèƒ½è€Œåˆä¸å…·å‡½æ•°è°ƒç”¨å’Œè¿”回开销的较好方法,但å®åœ¨æœ¬è´¨ä¸Šä¸æ˜¯å‡½æ•°ï¼Œå› 而è¦é˜²æ¢å®å±•å¼€åŽå‡ºçŽ°ä¸å¯é¢„料的结果,对å®çš„定义和使用è¦æ…Žè€Œå¤„之。很é—æ†¾ï¼Œæ ‡å‡†C至今没有包括C++ä¸inline函数的功能,inlineå‡½æ•°å…¼å…·æ— è°ƒç”¨å¼€é”€å’Œå®‰å…¨çš„ä¼˜ç‚¹ã€‚
使用寄å˜å™¨å˜é‡ã€å†…嵌汇编和活用ä½æ“作也是æ高程åºæ•ˆçŽ‡çš„有效方法。
除了编程上的技巧外,为æ高系统的è¿è¡Œæ•ˆçŽ‡ï¼Œæˆ‘们通常也需è¦æœ€å¤§å¯èƒ½åœ°åˆ©ç”¨å„ç§ç¡¬ä»¶è®¾å¤‡è‡ªèº«çš„特点æ¥å‡å°å…¶è¿è½¬å¼€é”€ï¼Œä¾‹å¦‚å‡å°ä¸æ–次数ã€åˆ©ç”¨DMAä¼ è¾“æ–¹å¼ç‰ã€‚
Power Breadboard,Breadboard Power Supply,Breadboard Power Supply Module,Breadboard Power Module
Cixi Zhongyi Electronics Factory , https://www.cx-zhongyi.com