Embedded C language (must read for getting started)

     

Table of contents

Data types of STM32

const keyword

static keyword

volatile keyword

extern keyword

 struct structure

enum

 typedef

#define

Callback

#ifdef 、#ifndef、#else  、#if    


Embedded development includes both the development of the underlying hardware and the development of upper-level applications, that is, the hardware and software of the system. The C language has both the advantages of assembly language operating the underlying layer and the strong functionality of high-level languages. It deserves to be an embedded language. mainstream language for development. In the process of STM32 development, whether it is register-based development or library-based development, in-depth understanding and mastery of functions, pointers, and structures of embedded C language is the key to learning STM32.
The structural characteristics of the embedded C language are as follows.
(1) The program is always executed from the main function, and the statement ends with a semicolon ";", using /*...*/ or // as a comment.

(2) Function is the basic structure of C language, and each C language program is composed of one or more functional functions.

(3) The function consists of two parts: the description part and the function body.
 

  函数名(参数)
  {
      [说明部分];
      函数体;
  }

(4) A C language program includes several source program files (.c files) and header files (.h files), where the .h header file mainly consists of preprocessing commands (including files, macro definitions, conditional compilation, etc.) and data Statements (global variables, function declarations, etc.); the c source file is mainly the implementation file of the function.
(5) The peripheral function modular design method is adopted. A peripheral function module includes a source file (.c file) and a header file (.h file). The .c file is used to realize the function of the specific peripheral function module. The .h header file is used to declare the parameters and functions of the peripheral function module.
      Embedded system development mostly adopts modular and hierarchical design ideas, and the system hierarchy structure is clear, which is convenient for collaborative development. Figure 1 is a block diagram of the basic software structure of the embedded system.

                            Figure 1 The framework diagram of the basic software structure of the embedded system

Data types of STM32


Data is the basic operation object of the embedded C language, and the data type refers to the storage method of data in the computer memory, such as integer type (storing integers), floating point type (storing real numbers), character type (storing characters ), pointers (storage addresses), and derived composite data types (such as arrays, structures, unions, and enumeration types). The data type of the embedded C language is shown in Figure 2.

                                                     Figure 2 Data types of embedded C language

      Due to the different lengths of data types defined by different CPUs, ARM and other semiconductor manufacturers have formulated a unified CMSIS software standard, which pre-defines relevant data types, and ST also provides developers with C language-based standards. Peripheral library, its defined data types are shown in Table 1. For related source code, please refer to the stdint.h header file of STM32 standard peripheral library v3.5.0.
      The stm32f10x.h header file also explains the data types used by the previous version of the standard peripheral library. The v3.5.0 version no longer uses these old data types. In order to be compatible with the previous version, the new version provides a compatible description ,As shown in Figure 3.

                                                  Table 1 Data types defined by STM32
 

                                 Figure 3 STM32 standard peripheral library data type compatibility description

_I, _O, and _IO in Figure 3 are IO type qualifiers, and the kernel header file core_cm3.h defines the IO type qualifiers used by the standard peripheral library, as shown in Table 2. Note that the IO type qualifiers are underlined to avoid naming conflicts.
The data types in Table 1 are combined with the IO type qualifiers in Table 2. They are often used to define registers and structure variables in the standard peripheral library. Figure 4 shows the register definitions of related peripherals in the stm32f10x.h header file.

                                                Table 2 IO type qualifiers of STM32

                                              Figure 4 Register definitions of related peripherals in the stm32f10x.h header file

       Combining Table 2 and Figure 3, it can be seen that the same data type has multiple representations, such as unsigned 8-bit integer data, there are three representations: unsigned char, uint8_t, and u8. In different ST standard peripheral library versions, this All three representations can represent unsigned 8-bit integer data. Beginners should understand these three representations. The latest version v3.5.0 adopts the C99 standard of the CMSIS software standard, namely the uint8_t method.

const keyword

      The const keyword is used to define a read-only variable whose value cannot be changed at compile time. Note that the const keyword defines a variable rather than a constant.
      The const keyword is used to prevent the value of the variable from being modified by mistake during compilation, and at the same time improve the security and reliability of the program. It is generally placed in the header file or the beginning of the file.
      In the C99 standard, variables defined by the const keyword are global variables. The const keyword is different from the #definc keyword. The #define keyword is just a simple text replacement, and the variable defined by the const keyword is stored in static memory. The form of defining a constant using the #define keyword is

#define PI3.14159


      After using this method to define, no matter where PI is used, it will be replaced by 3.14159 by the preprocessor. The compiler does not perform type checking on PI. If it is used carelessly, errors may be introduced by preprocessing, and such errors are difficult Discover. Although the way of declaring variables with const increases the allocation space, it can eliminate errors introduced by preprocessing well, and provides a good form of type checking to ensure safety.
When programming with the const keyword, you need to pay attention to the following three points.
(1) Variables declared with the const keyword can only be read and cannot be assigned. like:

const uint8t sum = 3.14;
uint8_t abs=0;

...
sum= abs;//非法,将导致编译错误,因为sum 只能被读取,不能赋值
abs- sum: //合法


(2) The variable modified by the const keyword must be initialized when it is declared. The above statement indicates that the sum value is 3.14, and the sum value cannot be modified during compilation. If the sum value is directly modified during compilation, the compiler will prompt an error.
(3) The formal parameter of the function is declared as const, which means that the content pointed to by the passed pointer can only be read and cannot be modified. Such as the function int strlen(const char*str) used to count the length of strings in the standard function library of C language.


static keyword

      In the embedded C language, the static keyword can be used to modify variables, and variables modified with the static keyword are called static variables.
      Static variables are stored in the same way as global variables, which are all static storage methods. The scope of global variables is the entire source program. When a source program is composed of multiple source files, the global variables are valid in each source file, that is, a global variable is defined in a source file. To use this global variable in a source file, you only need to declare the global variable through the extern keyword in the source file to use it. If the keyword static is added before the global variable, the global variable is defined as a static global variable, and its scope of action is only valid in the source file defining the variable, and other source files cannot reference the global variable, thus avoiding It eliminates errors caused by referencing variables with the same name in other source files, which is conducive to modular programming.
      When programming with the static keyword, you need to pay attention to the following points.
      (1) The static keyword can be used not only to modify variables, but also to modify functions. In modular programming, if a function is declared static, the function can only be called by other functions in the module, for example:
 

      #include "stm32f1xx_hal .h”
      static void DMA_SetConfig (DMA_HandleTypeDef *hdma,uint32_t SrcAddress,uint32_t DstAddress, uint32_t DataLength);
...
      HAL_statusTypeDef HAL_DMA_start_IT(DMA_HandleTypeDef *hdma, uint32_t SrcAddress, uint32_t DstAddress, uint32_t DataLength)
{
      HAL_StatusTypeDef status- HAL_OK;”
.... ...
      if(HAL_DMA_STATE_REA.DY m- hdma->state)
  {
        DMA_Setconfig(hdma, SrcAddress, DstAddress, DataLength);
       ... ...
  }
      ... ...
}

 The above code is the source file stm32f1xx_hal_dma.c of the DMA module. If the DMA_SetConfig() function is declared as a static function using static, the DMA_SetConfig) function can only be called by other functions in stm32flxx_hal_dma.c, and cannot be used by files of other modules , that is, a local function is defined, which effectively avoids errors caused by other module files defining functions with the same name, and fully embodies the modular design idea of ​​the program.
(2) In addition to defining static global variables, static is also used to define static local variables to ensure that static local variables are not reinitialized during the call. Typical application cases include the realization of counting and statistics functions.

void fun_count()
{
      static count_num=0;
      //声明一个静态局部变量,count_num用作计数器,初值为0
      count_num++;
      printf("%d\n",count_num) :
}
int main(void)
(
     int i=0;
     for( i=0;i<=5;i++)
  {
     fun_count();
  }
     return 0;
}

Every time the fun_count() function is called in the main function, the static local variable count_num is incremented by 1 instead of being initialized to the initial value 0 every time.


volatile keyword


      In embedded development, the volatile keyword is commonly used, which is a type modifier and means "variable". The way to use it is as follows:

volatile char i;


      Here, the volatile keyword is used to define a character variable i, which indicates that i may change at any time, and must be read from the address of i every time the variable is used.
      Since the read/write speed of the memory is far lower than the read/write speed of the registers in the CPU, in order to improve the access speed of data information, on the one hand, a high-speed cache is introduced on the hardware, and on the other hand, a compiler is used to optimize the program on the software. , read the value of the variable from the memory to the register of the CPU in advance, and when the variable is used later, read it directly from the faster register, which is beneficial to improve the operation speed, but at the same time there may be risks, such as The value of the variable in memory may be modified or overwritten by other parts of the program (such as other threads), while the previous value is still stored in the register, which leads to the inconsistency between the value read by the application program and the actual variable value; it is also possible It is the value in the register that has changed, but the value of the variable in the memory has not been modified, which will also lead to inconsistencies. Therefore, in order to prevent incorrect data from being read due to the optimization of the program by the compiler, the volatile keyword is used for definition.
      Simply put, using the volatile keyword means not allowing the compiler to optimize, that is, every time a value is read or modified, it must be read or modified from memory again, instead of using a backup saved in a register.
      To give a simple example: the award/bursary in the university is generally transferred directly to the school, and the school then distributes it to each student. The school finance department has registered the bank card number of each student, but inevitably there will be some The student loses the bank card or no longer uses this bank card due to various reasons, and has not had time to go to the financial office to re-register, thus affecting the issuance of awards/bursaries. Here, the student is the original address of the variable, and the bank card number of the financial office is The variable is backed up in the register, use the volatile keyword to define the student variable, so that every time the award/bursary is issued, the original address of the student variable will be found instead of directly transferred to the bank card saved by the financial department, thereby avoiding Mistakes happen.
       The meaning of the const keyword is "read-only", and the meaning of the volatile keyword is "changeable", but the volatile keyword is interpreted as "direct access to the original memory address" is more appropriate. After using the volatile keyword to define the variable, the Variables will not change due to external factors. In general, the volatile keyword is commonly used in the following situations.
     (1) Variables modified in the interrupt service routine for detection by other programs need to use the volatile keyword.

     (2) The volatile keyword should be added to the flags shared between tasks in a multi-tasking environment.

  (3) The hardware registers of the peripheral register address mapping are usually declared with the volatile keyword.


extern keyword


       The extern keyword is used to indicate that this function or variable is defined in other files, prompting the compiler to find its definition in other modules when encountering this function or variable. In this way, the function or variable declared by the extern keyword can be used in this module or other modules. Therefore, using the extern keyword is a declaration rather than a redefinition. The method of use is as follows:

extern int a;
extern int  funA( ):


      Analysis: The first statement is only a declaration of variable a, rather than defining variable a, and does not allocate memory space for a. As a global variable, variable a can only be defined once. The second statement declares the function funA(), which is already defined in another file.
       In STM32, the extern keyword also has an important role, that is, it is used together with "C", that is, extern "c", for link specification. For example, the stm32f10x.h header file has the following code.

#ifndef _STM32F10× H
#define _STM32F10x_H
#ifdef .epluspius
extern "C"{
#endif
...
#ifdef _eplusplus
}
"endif

       The meaning of this code is, if _STM32F10x_H is not defined, then define _STM32F10x H, if _cplusplus has been defined, then execute the statement in extern "C", extern "C" is to tell the C++ compiler that the program code in brackets is according to The file format of C language is compiled, _cplusplus is a custom macro in the C++ compiler, and plus means "+".
C+H+ supports function overloading, that is, the function name and parameters will be combined to generate a new intermediate function name at compile time, while C language does not support function overloading, which leads to links when using C functions in the C++ environment When the corresponding function cannot be found, you need to use extern "C" to specify the link to tell the compiler that the function defined in C language is used at this time, and the naming rules of C language should be used to process the function. Linked intermediate function name.
       Generally, the function declaration is stored in the header file. When the function may be used by C language or C+, the function declaration is stored in extern "C" to avoid compilation errors. The complete usage method is as follows:
 

#ifdef__cplusplus
   extern "C"{
    #endif
   //函数声明
    #ifdef_Cplusplus
     }
#endif

       Many header files in STM32 use this usage, such as stm32f1 0x_adc.h, stm32f10x can.h, stm32f1Ox_gpio.h in the standard peripheral library.
      Note the following points when programming using the extern keyword.
      Embedded development generally adopts a modular design idea. Therefore, in order to ensure the use of global variables and functional functions, the extern keyword is generally used in the .h header file to declare the external functions and variables that a module provides for other modules to call. In actual programming, you only need to include the .h header file into the corresponding .c file of the module, that is, add the code #include "xxx.h" to the .c file of the module. Examples are as follows:

 struct structure

      Struct is used to define a structure type, and its function is to combine data of different data types to construct a new data type. The general usage of struct is as follows:

 struct  结构体名
	{
	 数据类型   成员名1;
	 数据类型   成员名2;
	 数据类型   成员名n;
	 };
struct Student{         //声明结构体
    char name[20];      //姓名
    int num;            //学号
    float score;        //成绩
};

enum

       Sometimes a variable has several possible values, such as 7 days in a week, courses offered in each semester, 12 different colors (red, orange, yellow, green, blue, blue, purple, gray, pink, black , white, brown), etc., C language provides an enum enumeration type, which is used to list all possible values ​​of variables or objects one by one, and the value of variables is limited to the listed values. The enumeration type is used as follows:
 

enum枚举名
{
枚举成员1,
枚举成员2,
...
枚举成员n;
}枚举变量;

      The enum enumeration type is a collection, enclosing all possible values ​​in curly braces, the enumeration members in the curly braces are separated by commas, and the comma is omitted after the last enumeration member. The enumeration type ends with a semicolon, and the enumeration variable here can be omitted, and then defined according to the enumeration name when needed later.
For example, use the enum enumeration type to list several common colors.
 

enum Color
{
RED,
GREEN,
BLACK,
YELLOw
};

      The above-mentioned enumeration type named Color has only 4 members: RED, GREEN, BLACK, YELLOW, which means that the value of the Color type variable can only take one of these 4 colors.
      For example, use enum to define a Weekdays enumeration type name, including 7 enumeration members: from Monday to Sunday, and define enumeration variables Mydays and Olddays.

enumweekdays
{
Monday=1,
Tuesday,
wednesday,
Thursday,
Friday,
Saturday,
sunday
}Mydays.olddays;

      Note: the enum enumeration type has automatic numbering function, the default value of the first enumeration member is integer 0, the value of subsequent enumeration members is automatically added to the value of the previous member, and the enumeration member can also be customized Value, if the value of the first enumeration member is defined as 1, then the value of the second enumeration member is 2, and so on, such as the value of Friday in the above example is 5. Therefore, the value of the enumeration member in the enum enumeration type is a constant rather than a variable. You cannot use an assignment statement to assign a value to it in the program, but you can assign the enumeration value to the enumeration variable.
For example, the following two statements are correct.

Mydays=Thursday;
olddays=Friday;


      But the following two statements are wrong.

Tuesday=o;
Mydays=1;

 typedef

      typedef is used to define a simple alias for a complex declaration, it is not really a new type. There are generally two purposes of using typedef in programming: ① give variables a new name that is easy to remember and have clear meaning; ② simplify some more complex type declarations. Its basic format is as follows:
typedef type name custom alias;
for example:

typedef signed char int8_t;//为数据类型signed char起别名int8_t
typedef signed int int32_t;//为数据类型signed int起别名int32_t


      In STM32 development, typedef mainly has the following three usages.
      1. The basic application of typedef
      is a simple alias for a known data type, as in the above example.
      2. Typedef is used in combination with structure struct
      This usage is used for custom data types. Such as the GPIO initialization structure GPIO_InitTypeDef in the stm32f10x_gpio.h header file.

typedef struct
{
   uint16_t GPIO_ Pin;
   GPIOSpeed_TypeDef GPIO_Speed;
   GPIOMode TypeDef GPIO_Mode;
}IGPIo_InitTypeDef;


       The above statement uses struct to create a new structure. This new structure has three members GPIO_Pin, GPIO_Speed ​​and GPIO_Mode. At the same time, it uses typedef to define a new name GPIO_InitTypeDef for this newly created structure. You can use GPIO_InitTypeDef directly in the application to define variables. For example:

GPIO_InitTypeDef GPIO_ InitStrueture;


       The above statement uses the GPIO_InitTypeDef structure to define a variable GPIO_InitStructure, and the method of referencing the three members is as follows:

GPIO InitStructure.GPIO_Pin;
GPIO_InitStructure.GPIO_Speed;
GPIO InitStructure.GPIO Mode;

3. Use typedef in combination with enum
      Use the typedef keyword to define the enumeration type as an alias, and use the alias to declare variables. There are many applications for the combination of enum and typedef in the v3.5.0 version of the STM32 standard peripheral library. The code in the stm32f10x_gpio.h header file is as follows.

Typedef enum
{
GPIO Speed_1OMHz=1,
GPIo_Speed_2MHz,
GPIOSpeed_50MHz;
}GPIOSpeed_TypeDef;


       In this example, the enum enumeration type has three members: GPIO Speed_10MHz, GPIO Speed_2MHz, and GPIO_Speed_50MHz, and the first enumeration member GPIO_Speed_10MHz is assigned a value of 1, and the enum enumeration type will assign the enumeration member to the first enumeration Add 1 to the member assignment, so the default value of GPIO_Speed_2MHz is 2, and the default value of GPIO_Speed_50MHz is 3. At the same time, use the typedef keyword to define an alias GPIOSpeed ​​TypeDef for this enumeration type. Here, the enumeration name of the enumeration type is omitted, and only use typedef to define an alias for the enumeration type.


#define

      #define is a preprocessing command of C language, it is used for macro definition, it is used to define an identifier as a string, the identifier is called macro name, and the defined string is called replacement text, which is defined by macro The purpose is mainly to facilitate program writing, generally placed in front of the source file, called the preprocessing part.
      The so-called preprocessing refers to the work done before compilation. Preprocessing is an important function of C language, which is completed by the preprocessing program. When the program is compiled, the system will automatically refer to the preprocessing program to process the preprocessing part of the source program, and automatically start compiling the source program after processing.
      In the STM32 standard peripheral library, #define is mainly used in the following two ways.
1. No-argument macro definition
The general form of no-argument macro definition is as follows:

#define<宏名>字符串>


Among them, the string can be a constant, a character string, an expression, etc.
       For example: #define UINT8_MAX 255
       This statement means that the macro name UINT8_MAX is defined, which stands for 255. For example: #define_IO volatile;
       This statement means that the macro name _IO is defined, which means volatile. If you need to use volatile in the program in the future, you can use IO.
       For example: #define RCC AHBPeriph_DMA1 ((uint32_t) 0x00000001)
       This statement means to define the RCC_AHBPeriph_DMA1 macro name, representing 32-bit unsigned data 0x00000001.

       There are many such usages in STM32, such as the definition of the APB2_peripheral peripheral base address in the stm32f1 0x_rcc.h file of the standard peripheral library v3.5.0, as shown in Figure 5.

                          Figure 5 Definition of the base address of each peripheral in APB2_peripheral

2. Macro definition with parameters
The macro definition format is as follows:

#define<宏名>(参数1,参数2,…,参数n)<替换列表>

For example:

define SUM(x,y) (x+y)
a=SUM(2,2):


Among them, the result of a is 4, define SUM(X,y) as x+y, and replace SUM(x,y) with xty during precompilation.
For example:

#define IsGPIO_SPEED(SPEED)(((SPEED) = GP1o_Speed_10MHz)||((SPEED)==GPIO_Speed_ 2MHz)||((SPEED)==GP10_Speed_50MHz))


Use macro definition #define to replace IS_GPIO_SPEED(SPEED) with GPIO_Speed_10MHz, GPIO_Speed_2MHz or GPIO_Speed_50MHz.
      Note: The macro definition with parameters is also just a simple character replacement, the replacement is performed before compilation, the expansion does not allocate memory units, and does not perform value transfer processing, so the replacement does not take up runtime, only takes up compilation time , so this method can improve the operating efficiency.
       The difference between #define and typedef is: typedef is processed in the compilation phase and has the function of type checking, while #define is processed in the preprocessing phase, that is, before compilation, only simple string replacement is performed without any examine.


Callback

       A callback function is a function that is called through a function pointer. Some functions in the operating system often need to call user-defined functions to realize their functions. Since the direction of calling system functions is opposite to that of commonly used user programs, this call is called callback (Callback), and is called by system functions The function is called a callback function.
      The HAL library of STM32 defines the corresponding callback function in the stm32flxx_hal_xxx.c file, which is triggered by the interrupt, and its essence is the interrupt handler. For example, in the stm32flxx_hal_gpio.c code, the corresponding callback function HAL_GPIO_EXTICallback(GPIO_Pin) is called through the GPIO interrupt processing function voidHAL_GPIO_EXTI_IRQHandler(uint16_t GPIO_Pin). Developers only need to write applications in the callback function to realize the interrupt service function.

#ifdef 、#ifndef、#else  #if    


#define defines a preprocessing macro
#undef cancels the meaning of the macro
 #if compiles the conditional command in the preprocessing, which is equivalent to the if statement in C syntax
#ifdef judges whether a macro is defined, if it is defined, executes the subsequent statement
# ifndef Contrary to #ifdef, judge whether a macro is not defined
#elif If #if, #ifdef, #ifndef or the previous #elif condition is not satisfied, execute the statement after #elif, which is equivalent to else- in C syntax if #else corresponds to #if, #ifdef, #ifndef, if these conditions are not satisfied, execute the statement after #else, which is equivalent to the end of
else #endif #if, #ifdef, #ifndef in C syntax
Flag.
defined Used in conjunction with #if, #elif to determine whether a macro is defined

I won't repeat the pointer-related content here. There are a lot of rich information on the Internet.

Guess you like

Origin blog.csdn.net/qq_61672347/article/details/126760788