2025年嵌入式C语言(入门必看)

嵌入式C语言(入门必看)目录 STM32 的数据类型 const 关键字 static 关键字 volatile 关键字 extern 关键字 struct 结构体 enum typedef define 回调函数 ifdef ifndef else if 嵌入式开发中既有底层硬件的开发又涉及上层应用的开发 即涉及系统的硬件和软件

大家好,我是讯享网,很高兴认识大家。

     

目录

STM32的数据类型

const关键字

static 关键字

volatile关键字

extern关键字

 struct结构体

enum

 typedef

#define

回调函数

#ifdef 、#ifndef、#else  、#if    


嵌入式开发中既有底层硬件的开发又涉及上层应用的开发,即涉及系统的硬件和软件,C语言既具有汇编语言操作底层的优势,又具有高级语言功能性强的特点,当之无愧地成为嵌入式开发的主流语言。在 STM32开发过程中,不论是基于寄存器开发还是基于库开发,深入理解和掌握嵌入式C语言的函数、指针、结构体是学习STM32的关键。
嵌入式C语言的结构特点如下。
(1)程序总是从main函数开始执行,语句以分号“;”结束,采用/*…*/或//做注释。

(2)函数是C语言的基本结构,每个C语言程序均由一个或多个功能函数组成。

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

讯享网

(4)一个C语言程序包含若干个源程序文件(.c文件)和头文件(.h文件),其中.h头文件主要由预处理命令(包括文件、宏定义、条件编译等)和数据声明(全局变量、函数等声明)组成;c源文件主要是功能函数的实现文件。
(5)采用外设功能模块化设计方法,一个外设功能模块包括一个源文件(.c文件)和一个头文件(.h文件),.c文件用于具体外设功能模块函数的实现,.h头文件用于对该外设功能模块参数及功能函数的声明。
      嵌入式系统开发多采用模块化、层次化的设计思想,系统层次架构清晰,便于协同开发。图1为嵌入式系统的软件基本结构框图。

讯享网

                            图1 嵌入式系统的软件基本结构框架图

STM32的数据类型

                                                     图二 嵌入式C语言的数据类型

                                 图3  STM32标准外设库数据类型兼容说明

                                                表2   STM32的IO类型限定词

                                              图4 stm32f10x.h头文件中相关外设的寄存器定义

       结合表2和图3,可以看出同一数据类型有多种表示方式,如无符号8位整型数据有unsigned char、uint8_t、u8三种表示方式,在不同的ST标准外设库版本中这三种表示方式都可以表示无符号8位整型数据,初学者应了解这三种表达方式,最新的v3.5.0版本采用 CMSIS软件标准的C99标准,即 uint8_t方式。

const关键字

      const关键字用于定义只读的变量,其值在编译时不能被改变,注意,const关键字定义的是变量而不是常量。
      使用 const关键字是为了在编译时防止变量的值被误修改,同时提高程序的安全性和可靠性,一般放在头文件中或者文件的开始部分。
      在C99标准中,const关键字定义的变量是全局变量。const 关键字与#definc关键字存在区别,#define关键字只是简单的文本替换,而const关键字定义的变量是存储在静态存储器中的。使用#define关键字定义常量的形式为

讯享网#define PI3.14159


      使用该方式定义后,无论在何处使用PI,都会被预处理器以3.14159替代,编译器不对PI进行类型检查,若使用不慎,则很可能由预处理引入错误,且这类错误很难发现。用const声明变量的方式虽然增加了分配空间,但可以很好地消除预处理引入的错误,并提供了良好的类型检查形式,保证安全性。
利用 const关键字进行编程时需要注意以下三点。
(1)使用const关键字声明的变量,只能读取,不能被赋值。如:

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


(2) const关键词修饰的变量在声明时必须初始化,上述语句表示 sum值是3.14,且sum值在编译时不能修改,若在编译过程中直接修改sum值,则编译器会提示出错。
(3)函数的形参声明为const,则意味着所传递的指针指向的内容只能读,不能被修改。如C语言的标准函数库中用于统计字符串长度的函数 int strlen(const char*str)。


static 关键字

      在嵌入式C语言中,static关键字可以用来修饰变量,使用static关键字修饰的变量,称为静态变量。
      静态变量的存储方式与全局变量一样,都是静态存储方式。全局变量的作用范围是整个源程序,当一个源程序由多个源文件组成时,全局变量在各个源文件中都是有效的,即一个全局变量定义在某个源文件中,若想在另一个源文件中使用该全局变量,则只需要在该源文件中通过 extern关键字声明该全局变量就可以使用了。若在该全局变量前加上关键字static,则该全局变量被定义成一个静态全局变量,其作用范围只在定义该变量的源文件内有效,其他源文件不能引用该全局变量,这样就避免了在其他源文件中因引用相同名字的变量而引发的错误,有利于模块化程序设计。
      利用static关键字进行编程时需要注意以下要点。
      (1)static关键字不仅可以用来修饰变量,而且可以用来修饰函数。模块化程序设计中,若用static声明一个函数,则该函数只能被该模块内的其他函数调用,例如:
 

讯享网 #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); ... ... } ... ... } 
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; } 

在main函数中每调用一次 fun_count()函数,静态局部变量count_num加1,而不是每次都被初始化为初值0。


volatile关键字

讯享网volatile char i;


      这里使用volatile关键字定义了一个字符型的变量i,指出i是随时可能发生变化的,每次使用该变量时都必须从i的地址中读取。
      由于内存的读/写速度远不及CPU中寄存器的读/写速度,为了提高数据信息的存取速度,一方面在硬件上引入高速缓存Cache,另一方面在软件上使用编译器对程序进行优化,将变量的值提前从内存读取到CPU的寄存器中,以后用到该变量时,直接从速度较快的寄存器中读取,这样有利于提高运算速度,但同时也可能存在风险,如该变量在内存中的值有可能被程序的其他部分(如其他线程)修改或覆盖,而寄存器中存放的仍是之前的值,这就导致应用程序读取的值和实际变量值不一致;也有可能是寄存器中的值发生了改变,而内存中该变量的值没有被修改,同样也会导致不一致的情况发生。因此,为防止由于编译器对程序进行优化导致读取错误数据,使用 volatile关键词进行定义。
      简单地说,使用volatile关键字就是不让编译器进行优化,即每次读取或者修改值时,都必须重新从内存中读取或者修改,而不是使用保存在寄存器的备份。
      举个简单的例子:大学里的奖/助学金的发放一般都是直接转给学校,学校再发给每名学生,学校财务处都登记了每名学生的银行卡号,但不可避免地会有一些学生因各种原因丢失银行卡或不再使用这张银行卡,而没来得及去财务处重新登记,从而影响奖/助学金的发放,这里,学生就是变量的原始地址,而财务处的银行卡号就是变量在寄存器中的备份,使用 volatile关键字来定义学生这个变量,这样每次发放奖/助学金时都去找学生这个变量的原始地址,而不是直接转到财务处保存的银行卡上,进而避免错误的发生。
       const关键字的含义为“只读”,volatile关键字的含义为“易变的”,但volatile关键字解释为“直接存取原始内存地址”更为合适,使用 volatile关键字定义变量后,该变量就不会因外因而发生变化了。一般来说,volatile 关键字常用在以下场合。
     (1)中断服务程序中修改的、供其他程序检测的变量需要使用volatile关键字。

     (2)多任务环境下各任务间共享的标志应添加 volatile关键字。

  (3)外设寄存器地址映射的硬件寄存器通常要用volatile关键字进行声明。


extern关键字

extern int a; extern int funA( ):


      解析:第一条语句仅仅是变量a的声明,而不是定义变量a,并未为a分配内存空间,变量a作为全局变量只能被定义一次。第二条语句声明函数funA(),此函数已在其他文件中定义。
       STM32中,extern关键字还有一个重要作用,即与"C一起连用,即 extern "c",进行链接指定。例如,stm32f10x.h头文件中有如下代码。

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

       这段代码的含义是,若没有定义_STM32F10x_H,则定义_STM32F10x H,若已经定义_cplusplus,则执行 extern "C"中语句,extern "C"是告诉C++编译器括号中的程序代码是按照C语言的文件格式进行编译的,_cplusplus是C++编译器中自定义的宏,plus是“+”的意思。
C+H+支持函数重载,即在编译时会将函数名与参数联合起来生成一个新的中间函数名称,而C语言不支持函数重载,这就导致在C++环境下使用C函数会出现链接时找不到对应函数的情况,这时就需要使用extern "C"进行链接指定,告知编译器此时采用的是C语言定义的函数,需要使用C语言 的命名规则来处理函数,不要生成用于链接的中间函数名。
       一般将函数声明存放在头文件中,当函数有可能被C语言或C+使用时,将函数声明存放在 extern "C"中以免出现编译错误,完整的使用方法如下:
 

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

       STM32中很多头文件都采用这样的用法,如标准外设库中的 stm32f1 0x_adc.h ,stm32f10x can.h、 stm32f1Ox_gpio.h 等。
      利用extern 关键字进行编程时需要注意以下要点。
      嵌入式开发一般采用模块化设计思想,因此,为保证全局变量和功能函数的使用,extern关键字一般用在.h头文件中对某个模块提供给其他模块调用的外部函数及变量进行声明,实际编程中只需要将该.h头文件包含进该模块对应的.c文件中,即在该模块的.c文件中加入代码#include "xxx.h”。实例如下:

 struct结构体

      struct用于定义结构体类型,其作用是将不同数据类型的数据组合在一起,构造出一个新的数据类型。struct一般用法如下:

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

enum

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

      enum枚举类型是一个集合,将所有可能的取值用花括号括住,花括号中的各枚举成员之间用逗号隔开,最后一个枚举成员后省略逗号。enum枚举类型以分号结束,这里的枚举变量可以省略,在后面需要时再根据枚举名进行定义。
例如,利用enum枚举类型列举几种常见的颜色。
 

enum Color { RED, GREEN, BLACK, YELLOw };
讯享网enumweekdays { Monday=1, Tuesday, wednesday, Thursday, Friday, Saturday, sunday }Mydays.olddays; 
Mydays=Thursday; olddays=Friday;
讯享网Tuesday=o; Mydays=1;

 typedef

      typedef用于为复杂的声明定义一个简单的别名,它不是一个真正意义上的新类型。在编程中使用 typedef的目的一般有两个:①为变量起一个容易记忆且意义明确的新名称;②简化一些比较复杂的类型声明。其基本格式如下:
typedef类型名自定义的别名;
例如:

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


      STM32开发中,typedef主要有以下三种用法。
      1. typedef的基本应用
      为已知的数据类型起一个简单的别名,如上例。
      2. typedef 与结构体struct结合使用
      该用法用于自定义数据类型。如 stm32f10x_gpio.h头文件中的GPIO初始化结构体GPIO_InitTypeDef。

讯享网typedef struct { uint16_t GPIO_ Pin; GPIOSpeed_TypeDef GPIO_Speed; GPIOMode TypeDef GPIO_Mode; }IGPIo_InitTypeDef;
GPIO_InitTypeDef GPIO_ InitStrueture;
讯享网GPIO InitStructure.GPIO_Pin; GPIO_InitStructure.GPIO_Speed; GPIO InitStructure.GPIO Mode;

3. typedef 与 enum结合使用
      利用typedef关键字将枚举类型定义成别名,并利用该别名进行变量声明,STM32标准外设库v3.5.0版本中有很多enum和 typedef结合使用的应用。stm32f10x_gpio.h头文件中的代码如下。

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


#define

      #define是C语言的预处理命令,它用于宏定义,用来将一个标识符定义为一个字符串,该标识符称为宏名,被定义的字符串称为替换文本,采用宏定义的目的主要是方便程序编写,一般放在源文件的前面,称为预处理部分。
      所谓预处理是指在编译前所做的工作。预处理是C语言的一个重要功能,由预处理程序负责完成,程序编译时,系统将自动引用预处理程序对源程序中的预处理部分进行处理,处理完毕后自动进入对源程序的编译。
      STM32标准外设库中,#define的使用方式主要有以下两种。
1.无参数宏定义
无参数宏定义的一般形式如下:

讯享网#define<宏名>字符串>


其中,字符串可以是常数、字符串和表达式等。
       例如:#define UINT8_MAX 255
       该语句表示定义了宏名UINT8_MAX,它代表255,例如:#define_IO volatile;
       该语句表示定义宏名_IO,代表 volatile,若以后程序中再需要用到 volatile,则可以使用IO。
       例如:#define RCC AHBPeriph_DMA1 ((uint32_t)0x00000001)
       该语句表示定义RCC_AHBPeriph_DMA1宏名,代表32位的无符号数据0x00000001.

       STM32中有很多此类用法,如标准外设库 v3.5.0的 stm32f1 0x_rcc.h文件中APB2_peripheral外设基地址的定义,如图5所示。

                          图5  APB2_peripheral各外设基地址的定义

2.带参数的宏定义
宏定义格式如下:

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

例如:

讯享网define SUM(x,y) (x+y) … a=SUM(2,2):


其中,a的结果是4,将 SUM(X,y)定义为x+y,预编译时会将SUM(x,y)替换为xty。
例如:

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


使用宏定义#define 将 IS_GPIO_SPEED(SPEED)替换为 GPIO_Speed_10MHz、GPIO_Speed_2MHz或者GPIO_Speed_50MHz。
      注意:带参数的宏定义同样也只是进行简单的字符替换,替换是在编译前进行的,展开并不分配内存单元,不进行值的传递处理,因此替换不会占用运行时间,只占用编译时间,因此该方式可以提高运行效率。
       #define与 typedef的区别为:typedef是在编译阶段处理的,具有类型检查的功能,而#define是在预处理阶段处理的,即在编译前,只进行简单的字符串替换,而不进行任何检查。


回调函数

#ifdef 、#ifndef、#else  #if    


#define            定义一个预处理宏
#undef            取消宏的义
 #if                  编译预处理中的条件命令,相当于C语法中的if语句
#ifdef              判断某个宏是否被定义,若已定义,执行随后的语句
#ifndef            与#ifdef相反,判断某个宏是否未被定义
#elif                若#if, #ifdef, #ifndef或前面的#elif条件不满足,则执行#elif之后的语句,相当于C语法中的else-if
#else              与#if, #ifdef, #ifndef对应, 若这些条件不满足,则执行#else之后的语句,相当于C语法中的else
#endif             #if, #ifdef, #ifndef这些条件命令的结束标志.
defined          与#if, #elif配合使用,判断某个宏是否被定义

指针相关内容我这里就不在赘述了网上有很多丰富的资料。

小讯
上一篇 2025-01-19 18:08
下一篇 2025-01-25 16:35

相关推荐

版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容,请联系我们,一经查实,本站将立刻删除。
如需转载请保留出处:https://51itzy.com/kjqy/125217.html