首页 > 编程知识 正文

c语言嵌入式系统编程教程(嵌入式c语言是什么)

时间:2023-05-04 14:32:22 阅读:87903 作者:3443

目录

volatile用法在“结构”用法“枚举”预处理程序和预处理指令文件中,#include

volatile 用法

volatile的含义是“易于更改”,在嵌入式环境中用volatile关键字进行了声明由于该值具有“易变化”的特性,因此不会优化值的分配和检索操作。 由于这一特性,该关键字经常用于在嵌入式编译环境中解决编译器优化问题,分为以下三种情况:

硬件寄存器的修饰; 在包含用于修饰中断服务函数中的非自动变量的操作系统的项目中,对由多个APP修改的变量进行修饰; 修饰硬件寄存器

以STM32F103的HAL库函数中GPIO的定义为例,HAL库中GPIO寄存器的定义如下。

//*

* * @ brief通用采购I/o

*/

类型结构

{

_ io uint 32 _ t酷睿;

_ io单元32 _ t通道;

_ io单元32 _ t IDR;

_ io单元32 _ t顺序;

_ io单元32 _ t bsrr;

_ io单元32 _ t brr;

_ io单元32 _ t液晶显示器;

GPIO _类型化; 其中__IO的定义如下:

# #定义_ _ io volatile/*! 定义为定义读/写GPIO为:

# #定义位置((gpio _ typedef * ) GPIOA_BASE ) )。

#definegpiob((gpio_typedef* ) gpiob _基础) )。

#definegpioc((gpio_typedef* ) gpioc _基础) )。

# #定义((gpio _ typedef * ) gpiod _基础) )。

# #定义((gpio _ typedef * )定义基础) )。

#definegpiof((gpio_typedef* ) GPIOF_BASE ) )。

#definegpiog((gpio_typedef* ) GPIOG_BASE )在此定义APB2外围设备的基地址:

# # define AP B2 per iph _ base (per iph _ base0x 00010000 UL )最后,让我们来看看外围设备的基地址定义。

# # defineperiph _ base0x 40000000美元/*! 结合PeriPheral Base ADRES Inthe Aliasregion * /,逐一展开宏定义,仅从GPIOA来看,其他如下。

#defineGPIOA((gpio_typedef* )0x40000000ul0x00010000ul ) )如此定义,则gpioa的CRL地址为:

(volatile uint16_t * ) (0x 40000000 ul0x 00010000 ul0x 00000800 ul ) CRH的地址如下:

(volatile uint16_t * ) (0x 4000000 ul0x 00010000 ul0x 0000800 ul2)以后的寄存器也相同,因此在程序中使用。

GPIOA-CRH |=0x01; 实现的功能是提高GPIOA的CRH的寄存器的最低位。 在定义GPIO的寄存器结构体中不使用__IO uint16_t,而只使用uint16_t时,在程序中使用语句。

GPIOA-CRH |=0x01; 由编译器进行优化,可能无法执行此语句,从而无法实现将CRH提升到最低级别的功能; 但是,如果使用volatile修饰库函数,编译器不会优化此语句,每次执行此语句时都会从与CRH对应的内存地址中获取值或保存值,从而保证每次执行都有效。

在具有操作系统的项目中,可以修饰由多个任务修改的变量

在嵌入式开发中,不仅有单片机的裸机开发,还有带操作系统的开发,通常两者多采用c语言进行开发。 在具有RTOS、UCOS-II、Linux等操作系统的设计中,如果多个任务为同一变量赋值或取值,则这些变量也必须使用volatile进行修饰以确保可视性。 也就是说,您可以看到当前任务更改了该变量的值,同时其他任务也更改了该变量的值。

struct 用法

设计程序中最重要的步骤之一是选择表示数据的良好方法。 很多情况下,简单的变量和数组是不够的。 使用c结构变量进一步提高了表示数据的能力。 的结构基本形式可以灵活地表达多种数据,从而产生新的形式。

的结构声明格式如下:

结构名称

类型标识符的成员名称1;

类型标识符成员名称2;

类型标识符的成员名称n;

(; 该声明描述了一个由n个数据类型的成员组成的结构。 没有创建实际的数据对象,只描述对象是由什么构成的。 分析一下结构的声音

明的细节,首先是struct关键字,它表明跟在其后的是一个结构,后面是一个可选的标记,后面的程序中可以使用该标记引用该结构,因而我们可以在后面的程序中可以这样声明:

struct [结构体名] 结构体变量;

在结构体声明中用一对花括号括起来的是结构体成员列表。每个成员都用自己的声明来描述。成员可以是任意一种C的数据类型,甚至可以是其它结构。右花括号后面的分号是声明所必需的,表示该结构布局定义结束,例如:

struct students { char name[50]; char sex[50]; int age; float score; }; int main(void) { struct students student; printf("Name: %st",student.name[0]); printf("Sex: %st", student.sex); printf("Age: %dt", student.age); printf("Score: %frn", studen.score); return 0; }

可以把结构的声明放在所有函数的外部,也可以放在一个函数的内部。如果把一个结构声明在一个函数的内部,那么它的标记就只限于函数内部使用;如果把结构声明在所有函数的外部,那么该声明之后的所有函数都能使用它的标记。

结构有两层含义,一层含义是“结构布局”,如上述例子的structstudent{…};告诉编译器如何表示数据,但是它并未让编译器为数据分配空间;另一层含义是创建一个结构体变量,如上述例子的struct students student;编译器执行这行代码便创建了一个结构体变量student,编译器使用students模板为该变量分配空间:内含50个元素的char型数组1、50个元素的char型数组2,一个int型的变量和一个float的变量,这些存储空间都与一个名称为student结合在一起,如图 5.3.3 所示。

在内存中这个结构中的成员也是连续存储的。在通常程序设计中,struct还会与typedef一起使用,具体的会在后面的《typedef用法》一节介绍。

enum 用法

enum是C语言中用来修饰枚举类型变量的关键字。在C语言中可以使用枚举类型声明符号名称来表示整型常量,使用enum关键字可以创建一个新的“类型”并指定它可具有的值(实际上,enum常量是int类型,因此只要能使用int类型的地方就可以使用枚举类型)。枚举类型的目的是提高程序的可读性,其语法与结构的语法相同,如下:

enum [枚举类型名] { 枚举符 1, 枚举符 2 ... 枚举符 n, };

例如:

enum color { red, green, blue, yellow };

enum常量在上面的例子中,red, greeb, blue, yellow 到底是什么?从技术层面来讲,它们是 int 类型的整型常量,例如可以这样使用:

printf("red=%d, green=%d", red, green);

可以观察到最后打印的信息是:red=0,green=1。 red成为一个有名称的常量,代表整数0。类似的,其它的枚举符都是有名称的常量,分别代表1~3。只要是能使用整型常量的地方就可以使用枚举常量,例如,在声明数组的时候可以使用枚举常量表示数组的大小,在switch语句中可以把枚举常量作为标签。

enum默认值

默认情况下,枚举列表中的常量都被赋予0,1,2等,因此下面的声明中,apple的值是2:

enum fruit{banana, grape, apple};

如果只给一个枚举常量赋值,没有对后面的枚举常量赋值,那么后面的常量会被赋予后续的值,例如:

enum feline{cat, lynx=10, puma, tiger};

那么cat=0,lynx、puma、tiger的值分别是10、11、12。

typedef 用法

typedef工具是一个高级数据特性,利用typedef可以为某一类型自定义名称。这方面与#define类似,但是两者有三处不同:

与#define不同,typedef创建的符号只受限于类型,不能用于值;tyedef由编译器解释,不是预处理器;在其受限范围内,typedef比#define更灵活;

假设要用BYTE表示1字节的数组,只需要像定义个char类型变量一样定义BYTE,然后再定义前面加上关键字typedef即可:

typedef unsigned char BYTE;

随后便可使用 BYTE 来定义变量:

BYTE x, y[10];

该定义的作用域取决于typedef定义所在的位置。如果定义在函数中,就具有局部作用域,受限于定义所在的函数。如果定义在函数外面,就具有文件作用域。

为现有类型创建一个名称,看起来是多此一举,但是它有时的确很有用。在前面的示例中,用BYTE代 替unsigned char表明你打算用BYTE类型的变量表示数字而不是字符。使用typedef还能提高程序的可移植性。

用typedef来命名一个结构体类型的时候,可以省略该结构的标签(struct):

typedef struct { char name[50]; unsigned int age; float score; }student_info; student_info student={“Bob”, 15, 90.5};

这样使用typedef定义的类型名会被翻译成:

struct {char name[50]; unsigned int age; float score;} student = {“Bob”, 15, 90.5};

使用typedef的第二个原因是:tyedef常用于给复杂的类型命名,例如:

typedef void (*pFunction)(void);

把pFunction声明为一个函数,该函数返回一个指针,该指针指向一个void型。

使用typdef时要记住,typedef并没有创建任何新类型,它只是为某个已有的类型增加了一个方便使用的标签。

预处理器与预处理指令

本节将简单介绍C语言的预处理器及其预处理指令。首先是预处理指令,它们是:

#define、#include、#ifdef、#else、#endif、#ifndef、#if、#elif、#line、#error、#pragma

在这些指令中,#line、#error、#pragma在基础开发中比较少见,其它的都是在编程过程中经常遇到和经常使用的,所以我们在后面的章节将主要介绍这些常用的指令。

C语言建立在适当的的关键字、表达式、语句以及使用他们的规则上。然而C标准不仅描述C语言,还描述如何执行C预处理器。

C预处理器在执行程序之前查看程序,因而被称之为预处理器。根据程序中的预处理指令,预处理器把符号缩写替换成其表示的内容(#define)。预处理器可以包含程序所需的其它文件(#include),可以选择让编译器查看哪些代码(条件编译)。预处理器并不知道C,基本上它的工作是把一些文本转换成另外一些文本。

由于预处理表达式的长度必须是一个逻辑行(可以把逻辑行使用换行符‘’变成多个物理行),因而为了让预处理器得到正确的逻辑行,在预处理之前还会有个编译的过程,编译器定位每个反斜杠后面跟着换行符的示例,并删除它们,比如:

printf(“Hello, Chi na”);

转换成一个逻辑行:

printf(“Hello, China”);

另外,编译器把文本划分成预处理记号序列、空白序列和注释序列(记号是由空格、制表符或换行符分割的项),需要注意的是,编译器将用一个空格字符替换每一条注释,例如:

char/*这是一条注释*/str;

将变成:

char str;

这样编译处理后,程序就准备好进入预处理阶段,预处理器查找一行中以#号开始的预处理指令。然后我们就从#define指令开始讲解这些预处理指令。

文件包含#include

当预处理器发现#include预处理指令时,会查看后面的文件名并把文件的内容包含到当前文件中,即替换文件中的#include指令。这相当于把被包含文件的全部内容输入到源文件#include指令所在的位置。

#include指令有两种形式:

#include <stdio.h> // 文件名在尖括号内 #include “myfile.h” // 文件名在双引号内

在UNIX中,尖括号<>告诉预处理器在标准系统目录中寻找该文件,双引号“”告诉预处理器首先在当前目录(或指定路径的目录)中寻找该文件,如果未找到再查找标准系统目录:

#include <stdio.h> // 在标准系统目录中查找 stdio.h 文件 #include “myfile.h” // 在当前目录中查找 myfile.h 文件 #include “/project/header.h” // 在 project 目录查找 #include “../myheader.h” // 在当前文件的上一级目录查找

集成开发环境(IDE,比如开发板的开发环境keil)也有标准就或系统头文件的路径。许多集成环境提供菜单选项,指定用尖括号时的查找路径。

为什么我们要包含文件?因为编译器需要这些文件中的信息,例如stdio.h中通常包含EOF、NULL、getchar()和putchar()的定义。此外,该文件还包含C的其它的I/O函数。而对于我们自定义的文件,对于嵌入式开发来说,可能这些文件就有需要使用到的某些引脚宏定义、简单的功能函数宏定义等,以及某个源文件的全局变量和函数的声明等。

C语言习惯用.h后缀表示头文件,这些文件包含需要放在程序顶部的信息。头文件经常包含一些预处理指令,有些头文件由系统提供,也可以自定义。

下面是一个子自定义一个头文件的示例:gpio.h, main.c

/*gpio.h*/ #ifndef __GPIO_H #define __GPIO_H #include <stdio.h> typedef struct { uint8_t cnt; uint16_t sum; float result; }MyStruct; typedef enum { GPIO_RESET = 0, GPIO_SET = 1, }GPIO_STATE; #define ABS(x) ((x>0) ? (x) : (-x)) #endif/* main.c */ #include “gpio.h” int main(void) { MyStruct my_struct = {0, 25, 3.14}; GPIO_WritePin(GPIOA, GPIO_PIN_0, GPIO_SET); printf(“cnt=%d, sum=%d, result=%fnr”, my_struct.cnt, my_struct.sum, my_struct.result); }

#include指令也不是只包含.h文件,它同样也可以包含.c文件。

版权声明:该文观点仅代表作者本人。处理文章:请发送邮件至 三1五14八八95#扣扣.com 举报,一经查实,本站将立刻删除。