Taobao Store:【Wu Jianying’s Shop】
Address:【https://item.taobao.com/item.htm?_u=ukgdp5a7629&id=524088004171】

During the process of creating this 8*8 key program, whether writing my own or referencing others’ programs, I found that I lacked understanding of some basic concepts and programming standards in C language. Some issues stemmed from poor programming habits, while others were a sign of insufficient foundational knowledge, so I summarized them.
1. The Relationship Between .H Files and .C Files:
So far, the programs I’ve written have been quite simple, and I never thought about writing my own .H files or what their purpose is in relation to .C files. It was only recently, while writing a keyboard program and referencing others’ code, that I discovered that well-structured programs often include a “KEY.H” file which defines functions used in the .C file, such as Keyhit() and Keyscan().
After researching, I learned that .H files are header files, likely short for Head, which are necessary for structured program design. They allow for modularization of large programs and facilitate debugging across different modules.
1. Introduction to .H Files:
In microcontroller C programming, projects are generally structured by functional modularization. A project is divided into multiple functions, with the relevant programs for each function placed in a separate C program document, referred to as a module, and the corresponding filename is the module name. A module typically consists of two documents: one header file *.h that describes the data structures and function prototypes within the module, and another C file *.c that defines data instances or objects and implements the specific algorithms for the functions.
As part of project design, in addition to providing a detailed description of the overall functionality, each module is also defined in detail, including all module header files. Typically, the H header file defines 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 project members as a connectable OBJ file or a library file LIB. By not providing the source program documents, it can be publicly distributed, ensuring the ownership of the developers; on the other hand, it prevents unintentional or intentional modifications that could lead to inconsistencies and version chaos. Thus, the H header file serves as the basis for detailed project design and team task division, and it also provides functional descriptions for module testing. To reference data or algorithms within a module, simply include the specified module H header file using include.
3. Basic Composition of an .H File
/* The following is the header document for keyboard driver */
#ifndef _KEY_H_ // Prevents multiple inclusions; if _KEY_H_ is not defined, compile the next line
#define _KEY_H_ // This symbol is unique, indicating that once included, the symbol _KEY_H_ is defined
/////////////////////////////////////////////////////////////////
char keyhit( void ); // Key hit check
unsigned char Keyscan( void ); // Get key value
/////////////////////////////////////////////////////////////////
2. Prefer Using Macros #define
When I first looked at others’ programs, I noticed many #define statements at the beginning of the file after the includes. I thought it was cumbersome to have so many identifier replacements, and I didn’t understand the benefits of this approach. It turns out that using an identifier to represent a constant is beneficial for future modifications and maintenance. When changes are needed, you only have to modify the program’s beginning, and all instances in the program will be updated, 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
1. Macro names are generally written in uppercase.
2. Macro definitions are not C statements; they do not end with a semicolon.
3. Avoid Arbitrarily Defining Variable Types
In the past, when I needed a new variable, I would define it directly at the beginning of the program, whether inside or outside the function. Although this isn’t technically wrong, it’s not a recommended practice.
Now, let’s discuss the concepts related to variable types in C language:
Variables can be categorized based on their scope into local variables and global variables:
1. Global Variables: These are defined outside of functions. For example, the variables I previously defined at the beginning of the program are all global variables, which is a major misstep as I used too many global variables.
This leads to two issues: firstly, global variables consume resources throughout the program’s execution; secondly, having too many global variables reduces the program’s generality, as global variables are one of the reasons for coupling between modules.
2. Local Variables: These are defined within functions and are only valid inside those functions.
Based on the lifespan of variable values, they are classified into two types:
1. Static Storage Variables: These are allocated fixed storage space during the program’s execution.
2. Dynamic Storage Variables: These are allocated storage space dynamically according to needs during the program’s execution.
Specifically, there are four storage types: auto, static, register, extern
1. Local variables, unless specified otherwise, default to auto type, meaning dynamic storage. If not initialized, they will have an undefined value. If a local variable is defined as static, its value remains unchanged within the function, and its default value is zero.
static unsigned char sts; // Key status variable
static unsigned char Nowkeycode; // Current key code
static unsigned char Prekeycode; // Previous key code
static unsigned char Keydowntime; // Debounce time variable for rectangular keyboard press
static unsigned char Keyuptime; // Debounce time variable for rectangular keyboard release
static unsigned char Onoffdowntime; // Debounce time variable for shutdown key press
static unsigned char Onoffuptime; // Debounce time variable for shutdown key release
static unsigned char onoff_10ms; // Variable to judge the number of shutdown key interrupts, accumulating 150 times is approximately 3 seconds, as two 10ms interrupts have occurred before and after
2. Global Variables: These are allocated in the static storage area at compile time and can be referenced by various functions within the same file. If multiple files are involved, the variable from another file must be declared with extern in this file. However, if a global variable is defined as static, it can only be used within that single file.
4. The Use of Special Keywords const and volatile
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: To 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,
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
A variable defined as volatile indicates that it may be unexpectedly changed, so the compiler will not assume its value. Specifically, this means that the optimizer must carefully re-read this variable’s value each time it is used, instead of using a cached copy saved in a register.
/* Interrupt service routine. */
The intention of the program is to call the dosomething function when the ISR_2 interrupt occurs, but because the compiler determines that there has been no modification to i within the main function, it may only execute the read operation from i to a certain register once, and then each if check uses this register’s “copy of i”, causing dosomething to never be called. If the variable is marked as volatile, the compiler ensures that read and write operations on this variable will not be optimized away (they are guaranteed to execute).
Generally speaking, volatile is used in the following situations:
1. Variables modified in interrupt service routines that are to be detected by other programs should be marked volatile;
2. Flags shared between tasks in a multitasking environment should be marked volatile;
3. Memory-mapped hardware registers usually need to be marked volatile, as each read and write may have different meanings.
This article is sourced from the internet. If the original author does not support our reposting, please contact us for removal. Thank you!
If you like this article,please give us a thumbs up!
Technology is born from accumulation, success comes from persistence
—— Microcontroller Expert Wu Jianying
|