嵌入式C语言的结构特点

嵌入式开发中既有底层硬件的开发又涉及上层应用的开发,即涉及系统的硬件和软件,c语言既具有汇编语言操作底层的优势,又具有高级语言功能性强的特点,当之无愧地成为嵌入式开发的主流语言。在 stm32开发过程中,不论是基于寄存器开发还是基于库开发,深入理解和掌握嵌入式c语言的函数、指针、结构体是学习stm32的关键。嵌入式c语言的结构特点如下。
(1)程序总是从main函数开始执行,语句以分号“;”结束,采用/ … /或//做注释。
(2)函数是c语言的基本结构,每个c语言程序均由一个或多个功能函数组成。
(3) 函数由两部分组成:说明部分和函数体。
函数名(参数) { [说明部分]; 函数体; }(4)一个c语言程序包含若干个源程序文件(.c文件)和头文件(.h文件),其中.h头文件主要由预处理命令(包括文件、宏定义、条件编译等)和数据声明(全局变量、函数等声明)组成;c源文件主要是功能函数的实现文件。
(5)采用外设功能模块化设计方法,一个外设功能模块包括一个源文件(.c文件)和一个头文件(.h文件),.c文件用于具体外设功能模块函数的实现,.h头文件用于对该外设功能模块参数及功能函数的声明。嵌入式系统开发多采用模块化、层次化的设计思想,系统层次架构清晰,便于协同开发。图1为嵌入式系统的软件基本结构框图。
图1 嵌入式系统的软件基本结构框架图
1 stm32的数据类型数据是嵌入式c语言的基本操作对象,数据类型是指数据在计算机内存中的存储方式,如基本数据类型中的整型(存放整数)、浮点型(存放实数)、字符型(存放字符)、指针(存放地址)以及派生出的复合数据类型(如数组、结构体、共用体、枚举类型)。嵌入式c语言的数据类型如图2所示。
图二 嵌入式c语言的数据类型
由于不同cpu定义的数据类型的长度不同,因此arm公司联合其他半导体厂商制定了统一的cmsis 软件标准,这个标准中预先定义了相关的数据类型,st公司也为开发人员提供了基于c语言的标准外设库,其定义的数据类型如表1所示,相关源代码请参考stm32标准外设库v3.5.0的stdint.h头文件。stm32f10x.h头文件还对标准外设库之前版本所使用的数据类型进行了说明,v3.5.0版本已不再使用这些旧的数据类型,为了兼容以前的版本,新版本对其进行了兼容说明,如图3所示。
表1 stm32定义的数据类型
图3 stm32标准外设库数据类型兼容说明
图3中的_i、_o以及_io为io类型限定词,内核头文件 core_cm3.h定义了标准外设库所使用的io类型限定词,如表2所示。注意,io类型限定词加下画线是为了避免命名冲突。表1的数据类型与表2中的io类型限定词相结合,在标准外设库中常用来定义寄存器和结构体变量,图4为stm32f10x.h头文件中相关外设的寄存器定义。
表2 stm32的io类型限定词
图4 stm32f10x.h头文件中相关外设的寄存器定义
结合表2和图3,可以看出同一数据类型有多种表示方式,如无符号8位整型数据有unsigned char、uint8_t、u8三种表示方式,在不同的st标准外设库版本中这三种表示方式都可以表示无符号8位整型数据,初学者应了解这三种表达方式,最新的v3.5.0版本采用 cmsis软件标准的c99标准,即 uint8_t方式。
2 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)。
3 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); ... ... } ... ...}
上述代码为dma模块的源文件stm32f1xx_hal_dma.c,若利用static将dma_setconfig()函数声明为一个静态函数,则 dma_setconfig)函数只能被stm32flxx_hal_dma.c中的其他函数调用,而不能被其他模块的文件使用,即定义了一个本地函数,有效避免了因其他模块的文件定义了同名函数而引发的错误,充分体现了程序的模块化设计思想。(2) static除了用于定义静态全局变量,还用于定义静态局部变量,保证静态局部变量在调用过程中不被重新初始化。典型应用案例有实现计数统计功能。
void fun_count(){ static count_num=0; //声明一个静态局部变量,count_num用作计数器,初值为0 count_num++; printf(%dn,count_num) :}int main(void)( int i=0; for( i=0;i其中,字符串可以是常数、字符串和表达式等。例如:#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各外设基地址的定义
****9.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是在预处理阶段处理的,即在编译前,只进行简单的字符串替换,而不进行任何检查。
10 回调函数回调函数是一个通过函数指针调用的函数。操作系统中的某些函数常需要调用用户定义的函数来实现其功能,由于与常用的用户程序调用系统函数的调用方向相反,因此将这种调用称为回调(callback),而被系统函数调用的函数就称为回调函数。stm32的hal库在stm32flxx_hal_xxx.c文件中定义了相应的回调函数,并由中断触发,其实质是中断处理程序。如 stm32flxx_hal_gpio.c代码中通过gpio中断处理函数voidhal _gpio_exti_irqhandler(uint16_t gpio_pin)调用相应的回调函数hal_gpio_exticallback(gpio_pin),开发人员只需要在回调函数中编写应用程序就能实现中断服务功能。
**11 ** #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配合使用,判断某个宏是否被定义

积分运算电路的设计方法详细介绍
蓄电池检测仪使用教程_蓄电池检测仪测量什么
200元京东卡,邀您成为软件开发生产线 CodeArts体验官!
小米登上美国黑名单!股价暴跌
iPhone12会让部分卡片消磁?
嵌入式C语言的结构特点
PCB多层板的电磁兼容性设计
零线故障的原因_零线故障诊断方法_零线故障预防方法
英特尔调整管理层 任命赖恩·科兰尼奇为COO
云厂商和开源社区之间的“冲突”再一次爆发:Elastic修改开源协议
特朗普再回应:真的,不封杀华为!任正非:感谢特朗普推广华为
智能辅助驾驶渗透率达32.4% 支持L3级加快上市
日本建首支消防机器人队伍 耗时5年花费9000万元
智能手机市场出货大跌 华为、vivo逆增长
Linux Kernel 5.6-rc7候选版本发布
锂离子电池容量计算之电压法
基于无机塑性材料的超薄热电器件实物图及器件性能
IDC发布《IDC FutureScape:2020全球区块链市场预测——中国启示》
摩托罗拉Razr拆解图曝光采用了与三星Galaxy Fold类似的铰链结构
基于嵌入式技术的LED显示屏控制系统浅析