STM32开发基础知识之位操作、宏定义、ifdef条件编译、extern变量申明、typedef类型别名、结构体

本文主要是介绍STM32开发基础知识之位操作、宏定义、ifdef条件编译、extern变量申明、typedef类型别名、结构体,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

一、引言

本文将对STM32入门开发的基本C语言基础知识进行回顾和总结,一边学者在开发过程中能较顺利地进行。主要包括位操作、define宏定义、ifdef条件编译、extern变量申明、typedef类型别名、结构体等基本知识。

二、基础C语言开发知识总结

(一)、位操作

下面列举几类常见的位操作:

以上的这些位操作在单片机编程开发中到底有什么强大的用处呢?


(1) 可以在不改变其他值的情况下,对某几个位进行设值

相信学过51单片机的读者会时常听说过“取高八位”、“取低四位”、“将最高位置低”、“将最高位置高”……等等。其实我在初学51单片机的时候,听老师讲的一头雾水!直至了解了位操作的原理和使用方法后,才真正懂得了位操作的实用性。

下面进入正题:可以在不改变其他值的情况下,对某几个位进行设值!

方法就是先对需要设置的位用 “&” 操作符进行清零操作,然后用 “|” 操作符设值。比如我要改变 GPIOA 的状态:

1、可以先对寄存器的值进行&清零操作。

GPIOA->CRL&=0XFFFFFF0F; //将第 4-7 位清 0

2、然后再与需要设置的值进行|或运算

GPIOA->CRL|=0X00000040; //设置相应位的值,不改变其他位的值

(2)移位操作提高代码的可读性

移位操作在单片机开发中也非常重要,下面让我们看看固件库的 GPIO 初始化的函数里面的一行代码:


GPIOx->BSRR = (((uint32_t)0x01) << pinpos);
 

这个操作就是将 BSRR 寄存器的第 pinpos 位设置为 1,为什么要通过左移而不是直接设置一个固定的值呢?其实,这是为了提高代码的可读性以及可重用性。这行代码可以很直观明了的知道,是将第 pinpos 位设置为 1。如果你写成:

GPIOx->BSRR =0x0030;
 

这样的代码就不好看也不好重用了。
 

此外,类似这样的代码:

GPIOA->ODR|=1<<5; //PA.5 输出高,不改变其他位

这样我们一目了然, 5 告诉我们是第 5 位也就是第 6 个端口, 1 告诉我们是设置为 1 了。这就是当时学习51单片机的时候老师讲的将某一位置高,即置位高低。

3) ~取反操作使用技巧 

SR 寄存器的每一位都代表一个状态,某个时刻我们希望去设置某一位的值为 0,同时其他位都保留为 1,简单的作法是直接给寄存器设置一个值:

TIMx->SR=0xFFF7;

这样的做法设置第 3 位为 0,但是这样的做法同样不好看,并且可读性很差。看看库函数代码中怎样使用的:


TIMx->SR = (uint16_t)~TIM_FLAG;


而 TIM_FLAG 是通过宏定义定义的值:
#define TIM_FLAG_Update ((uint16_t)0x0001)
#define TIM_FLAG_CC1 ((uint16_t)0x0002)

看这个应该很容易明白,可以直接从宏定义中看出 TIM_FLAG_Update 就是设置的第 0 位了,可读性非常强。

(二)、define宏定义 

define 是 C 语言中的预处理命令,它用于宏定义,可以提高源代码的可读性,为编程提供
方便。常见的格式:


#define 标识符 字符串


“标识符”为所定义的宏名。“字符串”可以是常数、表达式、格式串等。例如:


#define SYSCLK_FREQ_72MHz 72000000
定义标识符 SYSCLK_FREQ_72MHz 的值为 72000000

 

关于define宏定义的理解,我个人觉得可以这样理解:给一个变量或者函数取一个别名。但是为什么要刻意去取别名呢,因为变量为具体数值时不方便记忆和阅读开发,所以取一个别名来代替的话更方便识别,同时更改变量时,也只需改数值即可,其名具有“行不更名坐不改姓”的通用性。

(三)、 ifdef 条件编译

单片机程序开发过程中,经常会遇到一种情况, 当满足某条件时对一组语句进行编译,而
当条件不满足时则编译另一组语句。
条件编译命令最常见的形式为:


#ifdef 标识符
程序段 1


#else
程序段 2


#endif
它的作用是什么呢?

它的作用是:当标识符已经被定义过(一般是用#define 命令定义),则对程序段 1 进行编译,否则编译程序段 2。 其中#else 部分也可以没有,即:

#ifdef
程序段 1
#endif

这个条件编译在MDK里面是用得很多的,在stm32f10x.h这个头文件中经常会看到这样的语句:


#ifdef STM32F10X_HD
大容量芯片需要的一些变量定义
#end

(四)、extern 变量申明

C 语言中 extern 可以置于变量或者函数前,以表示变量或者函数的定义在别的文件中,提示编译器遇到此变量和函数时在其他模块中寻找其定义。 这里面要注意,对于 extern 申明变量可以多次,但定义只有一次。在我们的代码中你会看到看到这样的语句:


extern u16 USART_RX_STA;


这个语句是申明 USART_RX_STA 变量在其他文件中已经定义了,在这里要使用到。所以,你肯定可以找到在某个地方有变量定义的语句:


u16 USART_RX_STA;


的出现。下面通过一个例子说明一下使用方法。
在 Main.c 定义的全局变量 id, id 的初始化都是在 Main.c 里面进行的。

Main.c文件

u8 id;//定义只允许一次
main()
{
id=1;
printf("d%",id);//id=1
test();
printf("d%",id);//id=2
}

但是我们希望在test.c的 changeId(void)函数中使用变量id,这个时候我们就需要在test.c里面去申明变量 id 是外部定义的了,因为如果不申明,变量 id 的作用域是到不了 test.c 文件中。看下面test.c 中的代码:

extern u8 id;//申明变量 id 是在外部定义的,申明可以在很多个文件中进行
void test(void){
id=2;
}

在 test.c 中申明变量 id 在外部定义,然后在 test.c 中就可以使用变量 id 了。

(五)、typedef 类型别名

typedef 用于为现有类型创建一个新的名字,或称为类型别名,用来简化变量的定义。typedef 在 MDK 用得最多的就是定义结构体的类型别名和枚举类型了。
 

struct _GPIO
{
__IO uint32_t CRL;
__IO uint32_t CRH;

};
这里定义了一个结构体GPIO,这样我们定义变量的方式为:

struct _GPIO GPIOA;//定义结构体变量 GPIOA

但是这样很繁琐, MDK 中有很多这样的结构体变量需要定义这里我们可以为结体定义一个别名 GPIO_TypeDef,这样我们就可以在其他地方通过别名 GPIO_TypeDef 来定义结构体变量了。方法如下:


typedef struct
{
__IO uint32_t CRL;
__IO uint32_t CRH;

} GPIO_TypeDef;

Typedef 为结构体定义一个别名 GPIO_TypeDef,这样我们可以通过 GPIO_TypeDef 来定义结构体变量:

GPIO_TypeDef _GPIOA,_GPIOB;

 这里的 GPIO_TypeDef 就跟 struct _GPIO 是等同的作用了。

(六) 、结构体

在32单片机的开发中,MDK 中太多地方使用结构体以及结构体指针,这回时刚开始学习32单片机的同学倍感陌生,一下子摸不着头脑。致使学习 STM32 的积极性大大降低,其实结构体并不是那么复杂。下面我稍微提及一下有关结构体的知识:

声明结构体类型:

Struct 结构体名{
        成员列表;
}变量名列表;

例如:

Struct U_TYPE {
        Int BaudRate
        Int WordLength;
}usart1,usart2;

在结构体申明的时候可以定义变量,也可以申明之后定义,方法是:


Struct 结构体名字 结构体变量列表 ;

例如: struct U_TYPE usart1,usart2;
结构体成员变量的引用方法是:


结构体变量名字.成员名


比如要引用 usart1 的成员 BaudRate,方法是: usart1.BaudRate;

结构体指针变量定义也是一样的,跟其他变量没有啥区别。
例如: struct U_TYPE *usart3; //定义结构体指针变量 usart1;
结构体指针成员变量引用方法是通过“->”符号实现,比如要访问 usart3 结构体指针指向的结
构体的成员变量 BaudRate,方法是:
Usart3->BaudRate;

上面讲解了结构体和结构体指针的一些知识,其他的什么初始化这里就不多讲解了。讲到这里,
有人会问,结构体到底有什么作用呢?为什么要使用结构体呢?下面我们将简单的通过一个实
例回答一下这个问题。

在我们单片机程序开发过程中,经常会遇到要初始化一个外设比如串口,它的初始化状态
是由几个属性来决定的,比如串口号,波特率,极性,以及模式。对于这种情况,在我们没有学习结构体的时候,我们一般的方法是:
void USART_Init(u8 usartx,u32 u32 BaudRate,u8 parity,u8 mode);

但是如果我们这个函数的入口参数是随着开发不断的增多,那么是不是我们就要不断的修改函数的定义呢?这是不是给我们开发带来很多的麻烦?那又怎样解决这种情况呢?这样如果我们使用到结构体就能解决这个问题了。我们可以在不改变入口参数的情况下,只需要改变结构体的成员变量,就可以达到上面改变入口参数的目的。

结构体就是将多个变量组合为一个有机的整体。上面的函数, BaudRate,wordlength,
Parity,mode,wordlength 这些参数,他们对于串口而言,是一个有机整体,都是来设置串口参数的,所以我们可以将他们通过定义一个结构体来组合在一个。 MDK 中是这样定义的:


typedef struct
{
        uint32_t USART_BaudRate;
        uint16_t USART_WordLength;
        uint16_t USART_StopBits;
        uint16_t USART_Parity;
        uint16_t USART_Mode;
        uint16_t USART_HardwareFlowControl;
} USART_InitTypeDef;

于是,我们在初始化串口的时候入口参数就可以是 USART_InitTypeDef 类型的变量或者指针变量了, MDK 中是这样做的:
void USART_Init(USART_TypeDef* USARTx, USART_InitTypeDef* USART_InitStruct);

这样做有什么好处吗? 

当然有好处,不然干嘛如此费劲咧~~~~~~~~~~~~~~~~~~

于是,我们在初始化串口的时候入口参数就可以是 USART_InitTypeDef 类型的变量或者指针变量了, MDK 中是这样做的:


void USART_Init(USART_TypeDef* USARTx, USART_InitTypeDef* USART_InitStruct);
这样,任何时候,我们只需要修改结构体成员变量,往结构体中间加入新的成员变量,而不需要修改函数定义就可以达到修改入口参数同样的目的了。 这样的好处是不用修改任何函数定义就可以达到增加变量的目的。


理解了结构体在这个例子中间的作用吗?在以后的开发过程中,如果你的变量定义过多,
如果某几个变量是用来描述某一个对象,你可以考虑将这些变量定义在结构体中,这样也许可以提高你的代码的可读性。使用结构体组合参数,可以提高代码的可读性,不会觉得变量定义混乱。当然结构体的作用就远远不止这个了,同时, MDK 中用结构体来定义外设也不仅仅只是这个作用,这里我只是举一个例子,通过最常用的场景,让大家理解结构体的一个作用而已。

(七)、结语 

以上就是我对单片机开发入门时应该掌握和回顾的知识总结,作为一个经历者和开发者,我希望读者一定不要忽视以上知识的重要性!

愿我们携手同行,逐梦不止!!!

冲冲冲……

这篇关于STM32开发基础知识之位操作、宏定义、ifdef条件编译、extern变量申明、typedef类型别名、结构体的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/459986

相关文章

IDEA编译报错“java: 常量字符串过长”的原因及解决方法

《IDEA编译报错“java:常量字符串过长”的原因及解决方法》今天在开发过程中,由于尝试将一个文件的Base64字符串设置为常量,结果导致IDEA编译的时候出现了如下报错java:常量字符串过长,... 目录一、问题描述二、问题原因2.1 理论角度2.2 源码角度三、解决方案解决方案①:StringBui

Android 悬浮窗开发示例((动态权限请求 | 前台服务和通知 | 悬浮窗创建 )

《Android悬浮窗开发示例((动态权限请求|前台服务和通知|悬浮窗创建)》本文介绍了Android悬浮窗的实现效果,包括动态权限请求、前台服务和通知的使用,悬浮窗权限需要动态申请并引导... 目录一、悬浮窗 动态权限请求1、动态请求权限2、悬浮窗权限说明3、检查动态权限4、申请动态权限5、权限设置完毕后

Python如何计算两个不同类型列表的相似度

《Python如何计算两个不同类型列表的相似度》在编程中,经常需要比较两个列表的相似度,尤其是当这两个列表包含不同类型的元素时,下面小编就来讲讲如何使用Python计算两个不同类型列表的相似度吧... 目录摘要引言数字类型相似度欧几里得距离曼哈顿距离字符串类型相似度Levenshtein距离Jaccard相

Python中顺序结构和循环结构示例代码

《Python中顺序结构和循环结构示例代码》:本文主要介绍Python中的条件语句和循环语句,条件语句用于根据条件执行不同的代码块,循环语句用于重复执行一段代码,文章还详细说明了range函数的使... 目录一、条件语句(1)条件语句的定义(2)条件语句的语法(a)单分支 if(b)双分支 if-else(

Go语言中三种容器类型的数据结构详解

《Go语言中三种容器类型的数据结构详解》在Go语言中,有三种主要的容器类型用于存储和操作集合数据:本文主要介绍三者的使用与区别,感兴趣的小伙伴可以跟随小编一起学习一下... 目录基本概念1. 数组(Array)2. 切片(Slice)3. 映射(Map)对比总结注意事项基本概念在 Go 语言中,有三种主要

使用Navicat工具比对两个数据库所有表结构的差异案例详解

《使用Navicat工具比对两个数据库所有表结构的差异案例详解》:本文主要介绍如何使用Navicat工具对比两个数据库test_old和test_new,并生成相应的DDLSQL语句,以便将te... 目录概要案例一、如图两个数据库test_old和test_new进行比较:二、开始比较总结概要公司存在多

基于Python开发PPTX压缩工具

《基于Python开发PPTX压缩工具》在日常办公中,PPT文件往往因为图片过大而导致文件体积过大,不便于传输和存储,所以本文将使用Python开发一个PPTX压缩工具,需要的可以了解下... 目录引言全部代码环境准备代码结构代码实现运行结果引言在日常办公中,PPT文件往往因为图片过大而导致文件体积过大,

使用DeepSeek API 结合VSCode提升开发效率

《使用DeepSeekAPI结合VSCode提升开发效率》:本文主要介绍DeepSeekAPI与VisualStudioCode(VSCode)结合使用,以提升软件开发效率,具有一定的参考价值... 目录引言准备工作安装必要的 VSCode 扩展配置 DeepSeek API1. 创建 API 请求文件2.

解决IDEA使用springBoot创建项目,lombok标注实体类后编译无报错,但是运行时报错问题

《解决IDEA使用springBoot创建项目,lombok标注实体类后编译无报错,但是运行时报错问题》文章详细描述了在使用lombok的@Data注解标注实体类时遇到编译无误但运行时报错的问题,分析... 目录问题分析问题解决方案步骤一步骤二步骤三总结问题使用lombok注解@Data标注实体类,编译时

Redis的Zset类型及相关命令详细讲解

《Redis的Zset类型及相关命令详细讲解》:本文主要介绍Redis的Zset类型及相关命令的相关资料,有序集合Zset是一种Redis数据结构,它类似于集合Set,但每个元素都有一个关联的分数... 目录Zset简介ZADDZCARDZCOUNTZRANGEZREVRANGEZRANGEBYSCOREZ