A good beginning is half the battle
Through the study of the previous chapter, I think you have mastered how to release the CPU in the program. I hope you can continue to stick to it. A good start is half the battle. Everything we do today is to do better in microcontroller programming.
Before talking about today's topic, let me talk about some of my previous experiences. When I first came into contact with C language programs, due to the limitations of learning content, the programs I wrote were not very large, generally only a few hundred lines. So all the programs were completed in one source file. I remember that when I participated in an electronic design competition in my freshman year, I debugged for more than a week. All the programs added up to about 1,000 lines. It took a long time to browse through a long file. Simple syntax errors were easy to locate, but other errors often took a long time to find. At that time, I began to know about modular programming and tried to write programs in modules. At first, some functions with the same function (such as the driver of 1602 LCD) were all written in a header file (.h) file, and then included in the place where they needed to be called, but I soon found that this method had its limitations and it was easy to make duplicate inclusion errors.
And it is also inconvenient to call. Soon the summer electronic design competition came, and the school conducted some training on our single-chip microcomputer software programming. Because the school has participated in national and provincial competitions over the years, it has accumulated a certain number of driver modules. In those days, the teacher would assign a certain amount of tasks every day, asking us to combine these modules to complete certain functions. And it was the training of modular programming in those days that gave me a deeper understanding of modular programming. And the program specifications began to pay attention slowly. In the days that followed, regardless of the size of the program, it was written in a modular programming way. For a long time, there have been single-chip microcomputer enthusiasts communicating with me on QQ. Sometimes, they would send me some problematic program source files and ask me to help modify them. It is also a long file, and the naming is extremely irregular. It is really painful to read it from the beginning. To be honest, it is really faster for me to rewrite one for them. This is true, because a certain amount of modules have been accumulated on hand. When completing a new system, it only needs to be completed quickly and conveniently according to the upper-level functional requirements and with the support of the underlying modules. There is no need to rewrite it brick by brick from beginning to end. From this, we can also see that one of the benefits of modular programming is its high reusability.
Let us now unveil the mystery of modularity and take a glimpse of its true face.
C language source file *.c
When it comes to C language source files, everyone is familiar with them. Because the program codes we usually write are almost all in this XX.C file. The compiler also compiles and generates the corresponding target file based on this file. As the basis of modular programming, the source code of all the functions we want to implement is in this file. Ideal modularization should be regarded as a black box. That is, we only care about the functions provided by the module, regardless of the implementation details inside the module. It's like we bought a mobile phone, we only need to know how to use the functions provided by the mobile phone, and we don't need to know how it sends text messages or how to respond to our key input. These processes are a black box for us users.
In large-scale program development, a program is composed of many modules. It is very likely that the writing tasks of these modules are assigned to different people. When you write this module, you may need to use the interface of the module written by others. At this time, what we care about is what kind of interface its module implements and how I can call it. As for how the module is organized internally, for me, there is no need to pay too much attention. What we need to pay attention to is to pursue the unity of the interface and shield unnecessary details from the outside as much as possible.
C language header file *.h
When it comes to modular programming, it is bound to involve multi-file compilation, that is, project compilation. In such a system, there are often multiple C files, and the functions of each C file are different. In our C file, since we need to provide an interface to the outside world, there must be some functions or variables provided to other external files for calling.
Suppose we have an LCD.C file, which provides the most basic LCD driving function
LcdPutChar(char cNewValue); //Output a character at the current position
But we need to call this function in another file, so how do we do it?
This is the role of the header file. It can be called an interface description file. The file should not contain any substantial function code. We can understand this header file as a manual, which describes the interface functions or interface variables provided by our module. At the same time, the file also contains some very important macro definitions and some structure information. Without this information, it is likely that the interface function or interface variable cannot be used normally. But the general principle is: information that should not be known to the outside world should not appear in the header file, and the information required for the outside world to call the interface function or interface variable in the module must appear in the header file, otherwise, the outside world cannot correctly call the interface function we provide. Therefore, in order for external functions or files to call the interface function we provide, it is necessary to include the interface description file we provide, that is, the header file. At the same time, our own module also needs to include this module header file (because it contains the macro definitions or structures required in the module source file). Just like the files we usually use are in triplicate, the module itself also needs to include this header file.
Let's define this header file. Generally speaking, the name of the header file should be consistent with the name of the source file, so that we can clearly know which header file describes which source file.
So we got the header file LCD.h of LCD.C, and its content is as follows.
#ifndef _LCD_H_#define _LCD_H_ extern LcdPutChar(char cNewValue);#endif
This is similar to defining a function in a source file. The difference is that the extern modifier is added in front of it to indicate that it is an external function that can be called by other external modules.
#ifndef _LCD_H_#define _LCD_H_#endif
These conditional compilation and macro definitions are to prevent duplicate inclusion. If there are two different source files that need to call the LcdPutChar(char cNewValue) function, they both include this header file through #include "Lcd.h". When the first source file is compiled, since _LCD_H_ has not been defined, the #ifndef _LCD_H_ condition is established, so _LCD_H_ is defined and the following statement is included. When the second file is compiled, since _LCD_H_ has been defined when the first file is included, #ifndef _LCD_H_ is not established, and the entire header file content is not included. Assuming there is no such conditional compilation statement, then both files include extern LcdPutChar(char cNewValue);, which will cause a duplicate inclusion error.
Typedef must be mentioned
Many friends seem to be accustomed to using the following statements in the program to define data types
#define uint unsigned int #define uchar unsigned char
Then use it directly when defining variables
uint g_nTimeCounter = 0;
It is undeniable that this is indeed very convenient, and it is also convenient for transplantation. But do you still think so considering the following situation?
#define PINT unsigned int * //define unsigned int pointer type PINT g_npTimeCounter, g_npTimeState;
So did you define two unsigned int pointer variables, or one pointer variable and one integer variable? And what was your original intention? Did you want to define two unsigned int pointer variables? If so, you will probably be looking for errors everywhere soon. Fortunately, C language has taken this into consideration for us. Typedef was born for this purpose. To give a variable an alias, we can use the following statement
typedef unsigned int uint16 ; //Give an alias to the pointer to an unsigned integer variable uint16typedef unsigned int * puint16 ; //Give an alias to the pointer to an unsigned integer variable puint16
When we define variables, we can define them like this:
uint16 g_nTimeCounter = 0; //define an unsigned integer variable puint16 g_npTimeCounter; //define a pointer to an unsigned integer variable
When we use C language to program in 51 MCU, the range of integer variables is 16 bits, while the range of integer variables in 32-bit microprocessors is 32 bits. If we want to port some code written in 8-bit MCU to 32-bit processor, then we may need to modify the type definition of variables everywhere in the source file. This is a huge task. In order to consider the portability of the program, we should develop a good habit of defining variables with aliases at the beginning.
For example, on an 8-bit microcontroller platform, there is a variable definition as follows
uint16 g_nTimeCounter = 0;
If porting to a 32-bit MCU platform, the range is still expected to be 16 bits.
You can directly modify the definition of uint16, that is
typedef unsigned short int uint16;
This is all you need, without having to search and modify the source files everywhere.
Define all commonly used data types in this way to form a header file, which is convenient for us to call directly in future programming.
File name MacroAndConst.h
Its contents are as follows:
#ifndef _MACRO_AND_CONST_H_#define _MACRO_AND_CONST_H_typedef unsigned int uint16; typedef unsigned int UINT; typedef unsigned int uint; typedef unsigned int UINT16; typedef unsigned int WORD; typedef unsigned int word; typedef int int16; typedef int INT16; typedef unsigned long uint32; typedef unsigned long UINT32; typedef unsigned long DWORD; typedef unsigned long dword; typedef long int32; typedef long INT32; typedef signed char int8; typedef signed char INT8; typedef unsigned char byte; typedef unsigned char BYTE; typedef unsigned char uchar; typedef unsigned char UINT8; typedef unsigned char uint8; typedef unsigned char BOOL;#endif
So far, it seems that we have a little idea about the division of labor between source files and header files and modular programming. So let's strike while the iron is hot and divide the LED flashing function we wrote in the previous chapter into modules and reorganize and compile it.
In the previous chapter, we mainly completed the function of making the LED driven by port P0 flash at a frequency of 1Hz. The timer and LED driver module were used. Therefore, we can simply divide the entire project into three modules: timer module, LED module, and main function.
The corresponding file relationship is as follows
main.c
Timer.c --?Timer.h
Led.c --?Led.h
Before we start to rewrite our program, let me first tell you how to create a project template in KEIL. I have been using this template until now. I hope it can give you some inspiration.
The following content is mainly based on pictures, supplemented by a small amount of text description.
Let's take the chip AT89S52 as an example.
OK, now a simple project template has been established. When we create new source files and header files in the future, we can save them directly to the src file directory.
Next we start writing each module file.
First, write Timer.c. The main content of this file is timer initialization and timer interrupt service function. Its content is as follows.
#includebit g_bSystemTime1Ms = 0 ; // 1MS system time stamp void Timer0Init(void) { TMOD &= 0xf0 ; TMOD |= 0x01 ; //Timer 0 working mode 1 TH0 = 0xfc; //Timer initial value TL0 = 0x66 ; TR0 = 1 ; ET0 = 1 ; }void Time0Isr(void) interrupt 1{ TH0 = 0xfc; //Re-initialize the timer TL0 = 0x66 ; g_bSystemTime1Ms = 1; //1MS time stamp flag is set}
Since we need to call our g_bSystemTime1Ms variable in the Led.c file, and the main function needs to call the Timer0Init() initialization function, we should make an external declaration for this variable and function in the header file to facilitate other function calls.
The contents of Timer.h are as follows.
#ifndef _TIMER_H_#define _TIMER_H_extern void Timer0Init(void) ;extern bit g_bSystemTime1Ms ;#endif
After completing the timer module, we start writing the LED driver module.
The content of Led.c is as follows:
#include#include "MacroAndConst.h"#include "Led.h"#include "Timer.h"static uint16 g_u16LedTimeCount = 0 ; //LED counterstatic uint8 g_u8LedState = 0 ; //LED status flag, 0 means on, 1 means off#define LED P0 //Define LED interface#define LED_ON() LED = 0x00 ; //All LEDs are on#define LED_OFF() LED = 0xff ; //All LEDs are offvoid LedProcess(void) { if(0 == g_u8LedState) //If the LED state is on, turn on the LED { LED_ON() ; } else //Otherwise turn off the LED { LED_OFF(); } }void LedStateChange(void) { if(g_bSystemTime1Ms) //System 1MS time stamp reaches { g_bSystemTime1Ms = 0; g_u16LedTimeCount++; //LED counter plus one if(g_u16LedTimeCount >= 500) //The count reaches 500, which means 500MS has passed, and the LED status is changed. { g_u16LedTimeCount = 0; g_u8LedState = ! g_u8LedState ; } } }
This module has only two external interfaces, so corresponding declarations need to be made in the corresponding Led.h.
Led.h content:
#ifndef _LED_H_#define _LED_H_extern void LedProcess(void) ;extern void LedStateChange(void) ;#endif
After these two modules are completed, we add their C files to the project and then start writing the code in the main function.
As follows:
#include#include "MacroAndConst.h"#include "Timer.h"#include "Led.h"sbit LED_SEG = P1^4; //digital tube segment selection sbit LED_DIG = P1^5; //digital tube position selection sbit LED_CS11 = P1^6; //led control bit void main(void) { LED_CS11 = 1; //74HC595 output enable LED_SEG = 0; //Disable the segment selection and bit selection of the digital tube (because they share the P0 port with the LED) LED_DIG = 0 ; Timer0Init() ; EA = 1 ; while(1) { LedProcess() ; LedStateChange(); } }
The screenshot of the entire project is as follows
This is the end of Chapter 3.
Let's summarize what we need to pay attention to.
1. What is the role of C language source files (*.c)?
2. What is the role of C language header files (*.h)?
3. The role of typedef
4. How to organize project templates
5. How to create a multi-module (multi-file) project
Previous article:Keil program debugging window
Next article:Modular Programming in KEIL
Recommended ReadingLatest update time:2024-11-16 15:47
Professor at Beihang University, dedicated to promoting microcontrollers and embedded systems for over 20 years.
- Innolux's intelligent steer-by-wire solution makes cars smarter and safer
- 8051 MCU - Parity Check
- How to efficiently balance the sensitivity of tactile sensing interfaces
- What should I do if the servo motor shakes? What causes the servo motor to shake quickly?
- 【Brushless Motor】Analysis of three-phase BLDC motor and sharing of two popular development boards
- Midea Industrial Technology's subsidiaries Clou Electronics and Hekang New Energy jointly appeared at the Munich Battery Energy Storage Exhibition and Solar Energy Exhibition
- Guoxin Sichen | Application of ferroelectric memory PB85RS2MC in power battery management, with a capacity of 2M
- Analysis of common faults of frequency converter
- In a head-on competition with Qualcomm, what kind of cockpit products has Intel come up with?
- Dalian Rongke's all-vanadium liquid flow battery energy storage equipment industrialization project has entered the sprint stage before production
- Allegro MicroSystems Introduces Advanced Magnetic and Inductive Position Sensing Solutions at Electronica 2024
- Car key in the left hand, liveness detection radar in the right hand, UWB is imperative for cars!
- After a decade of rapid development, domestic CIS has entered the market
- Aegis Dagger Battery + Thor EM-i Super Hybrid, Geely New Energy has thrown out two "king bombs"
- A brief discussion on functional safety - fault, error, and failure
- In the smart car 2.0 cycle, these core industry chains are facing major opportunities!
- The United States and Japan are developing new batteries. CATL faces challenges? How should China's new energy battery industry respond?
- Murata launches high-precision 6-axis inertial sensor for automobiles
- Ford patents pre-charge alarm to help save costs and respond to emergencies
- New real-time microcontroller system from Texas Instruments enables smarter processing in automotive and industrial applications
- A video reveals how difficult it is to make integrated circuits
- Design with diodes: Protect sensitive RF circuits and components [especially in the transceiver] [RX]
- About MSP430 interrupt mechanism
- How to set Keil compilation file not to compile a certain group file
- EEWORLD University ---- Black Gold ZYNQ FPGA Video Tutorial
- Take part in the quiz with prizes and win the industry’s red book “Low-Level Manual”!
- Are there any good EDA design tool manufacturers in China?
- How do I delete a post?
- Infrared remote control
- EEWORLD University ---- Introduction to the internal structure of FPGA (Intel official tutorial)