During the process of writing this 8*8 key program, whether I was writing it myself or referencing others’ programs, I found that I had many misunderstandings about some basic points of C language and programming norms. Some were due to my previous bad programming habits, and some were a manifestation of not having a solid foundation of knowledge. Therefore, I summarized the following insights.
1. The relationship between .H files and .C files:
So far, the programs I have written have all been very simple, and I have never thought about writing my own .H files, nor did I know what .H files were for or how they relate to .C files. Only recently, while writing a keyboard program and referencing others’ programs, did I find that strict programs written by others included a “KEY.H”, which defined functions that I had written in the .C file, such as Keyhit() and Keyscan().
After researching, I learned that .H files are header files, likely derived from the word ‘Head’. This is necessary for the structured design of programs, allowing for the modularization of large programs and enabling debugging across modules.
1. Introduction to .H files:
In microcontroller C program design, projects are generally structured by functional modules. Each project is divided into multiple functions, and the related programs for each function are placed in a C program document, referred to as a module, with the corresponding file name being the module name. A module typically consists of two documents: one is the header file *.h, which describes the data structures and function prototypes within the module; the other is the C file *.c, which defines data instances or objects and the specific implementation of function algorithms.
2. The role of .H files:
As part of project design, aside from providing a detailed description of the overall functionality of the project, it also defines each module in detail, which means providing the header files for all modules. Typically, H header files define the functionality of each function within the module, as well as the requirements for input and output parameters. The specific implementation of the module is designed, programmed, and debugged based on the H file. For confidentiality and security, once a module is implemented, it is provided to other members of the project in a connectable file format (OBJ) or library file format (LIB). Since the source program documents do not need to be provided, this allows for public distribution while ensuring the ownership of developers; on the other hand, it prevents others from intentionally or unintentionally modifying and causing inconsistencies, leading to version chaos. Therefore, H header files serve as the basis for detailed project design and team work division, as well as the functional description for testing modules. To reference data or algorithms within a module, one simply needs to include the specified module H header file.
3. Basic components of .H files:
/* The following is the header document for keyboard driver */
#ifndef _KEY_H_ // Prevent duplicate inclusion; if _KEY_H_ is not defined, compile the next statement
#define _KEY_H_ // This symbol is unique, indicating that once included, the symbol _KEY_H_ is defined
/////////////////////////////////////////////////////////////////
char keyhit(void); // Key press check
unsigned char Keyscan(void); // Get key value
/////////////////////////////////////////////////////////////////
#endif
2. Try to use macro definitions #define
When I started looking at others’ programs, I noticed that at the beginning of the program, after the file includes, there were many #define statements. At the time, I thought, is it really that troublesome to replace so many identifiers? I completely did not understand the benefits of this writing style. It turns out that using an identifier to represent a constant is beneficial for future modifications and maintenance; when modifying, you only need to change it at the beginning of the program, and all places where it is used will be modified, saving time.
#define KEYNUM 65 // Number of keys, used for Keycode[KEYNUM]
#define LINENUM 8 // Number of keyboard rows
#define ROWNUM 8 // Number of keyboard columns
Points to note:
1. Macro names are generally in uppercase
2. Macro definitions are not C statements and do not end with a semicolon
3. Do not define variable types randomly
In the past, when writing programs, whenever I needed a new variable, whether inside or outside a function, I would directly define it at the beginning of the program. Although this is not technically incorrect, it is an undesirable practice.
Now let’s discuss the concepts related to variable types in C language:
From the scope of a variable, it can be divided into local variables and global variables:
1. Global variables: Variables defined outside of functions. Many of the variables I previously defined at the beginning of the program are global variables, and here I made a big mistake by using too many global variables.
This brings two problems: first, global variables occupy resources throughout the program’s execution; second, having too many global variables reduces the generality of the program, as global variables are one of the reasons for coupling between modules.
2. Local variables: Variables defined within functions, which are only valid inside those functions.
From the perspective of the existence time of variable values, there are two types:
1. Static storage variables: Allocate fixed storage space during program execution.
2. Dynamic storage variables: Allocate storage space dynamically according to needs during program execution.
Specifically, there are four storage methods: auto, static, register, extern
1. Local variables, by default, are of type auto, meaning dynamic storage; if not initialized, they will have an indeterminate value. However, if a local variable is defined as static, its value remains unchanged within the function, and its default value is 0.
static unsigned char sts; // Key state variable
static unsigned char Nowkeycode; // Current key code
static unsigned char Prekeycode; // Previous key code
static unsigned char Keydowntime; // Debounce time variable for pressing the rectangular key
static unsigned char Keyuptime; // Debounce time variable for releasing the rectangular key
static unsigned char Onoffdowntime; // Debounce time variable for pressing the shutdown key
static unsigned char Onoffuptime; // Debounce time variable for releasing the shutdown key
static unsigned char onoff_10ms; // Variable for counting shutdown key interrupt times, accumulating 150 times is about 3 seconds, as it enters two 10ms interrupts
2. Global variables are allocated to the static storage area during compilation and can be referenced by various functions within the same file. If multiple files are involved, and a variable from one file is referenced in another file, the extern keyword must be used in the referencing file. However, if a global variable is defined as static, it can only be used within that single file.
4. Use of special keywords const and volatile
1. const
const is used to declare a read-only variable
const unsigned char a = 1; // Define a = 1; the compiler does not allow modification of a’s value
Purpose: Protect parameters that should not be modified
const unsigned char Key_code[KEYNUM] = {0x01,0x02,0x03,0x04,0x05,0x06,0x07,0x08,
0x09,0x0A,0x0B,0x0C,0x0D,0x0E,0x0F,0x10,
0x11,0x12,0x13,0x14,0x15,0x16,0x17,0x18,
0x19,0x1A,0x1B,0x1C,0x1D,0x1E,0x1F,0x20,
0x21,0x22,0x23,0x24,0x25,0x26,0x27,0x28,
0x29,0x2A,0x2B,0x2C,0x2D,0x2E,0x2F,0x30,
0x31,0x32,0x33,0x34,0x35,0x36,0x37,0x38,
0x39,0x3A,0x3B,0x3C,0x3D,0x3E,0x3F,0x40,
0x41}; // Key codes
const unsigned char Line_out[LINENUM] = {0xFE,0xFD,0xFB,0xf7,0xEF,0xDF,0xBF,0x7F}; // Row output encoding
const unsigned char Row_in[ROWNUM] = {0xFE,0xFD,0xFB,0xf7,0xEF,0xDF,0xBF,0x7F}; // Column input encoding
2. volatile
A variable defined as volatile indicates that this variable may be unexpectedly changed; thus, the compiler will not assume the value of this variable. Specifically, the optimizer must carefully read the value of this variable each time it is used, rather than using a backup stored in a register.
static int i = 0;
int main(void)
{
…
while (1)
{
if (i)
dosomething();
}
}
/* Interrupt service routine. */
void ISR_2(void)
{
i = 1;
}
The program’s intention is to call the dosomething function in main when the ISR_2 interrupt occurs. However, since the compiler determines that i has not been modified within the main function, it may only execute a read operation from i to a register once, and then each subsequent if check only uses the “i copy” in that register, causing dosomething to never be called. If the variable is marked as volatile, the compiler guarantees that all read and write operations to this variable will not be optimized (will definitely execute).
In general, volatile should be used in the following situations:
1. Variables modified in interrupt service routines that are detected by other programs should be marked as volatile;
2. Flags shared between tasks in a multitasking environment should be marked as volatile;
3. Memory-mapped hardware registers usually also need to be marked as volatile, as each read and write may have different meanings;
This article is reproduced from the internet, copyright belongs to the original author. If you find it unsatisfactory, please contact us for deletion!
Advertisement
About Lichuang Mall
Lichuang Mall (WWW.SZLCSC.COM) was established in 2011, dedicated to providing customers with one-stop online procurement services for electronic components, with lightning-fast delivery in 4 hours. It has over 10,000 square meters of modern component warehouse, with more than 160,000 types of stock inventory. The group’s full electronic industry chain self-operated services include: online EDA (LCEDA) + industry-leading PCB prototyping/small batch + component mall + stencil manufacturing + SMT chip mounting + electronic design education and solutions. As a vertical mall for electronic components with a complete range, self-operated inventory, and guaranteed quality, all components from Lichuang Mall are sourced from original manufacturers or authorized distributors, ensuring authentic products and providing you with professional one-stop electronic component procurement services.