为方便更好的学习STM32单片机,本篇博客主要总结STM32的入门基础知识,重点在于理解寄存器以及存储器映射和寄存器映射,深刻体会STM32是如何组织和管理庞大的寄存器,从而提高开发效率的,为后面的基于标准库的开发做好铺垫,务必认真学习,深刻理解。俗话说得好地基不牢地动山摇,要想学好单片机必须要非常熟悉底原理,毕竟我们这是底层开发所以接触到越底层越好,能让我们知道每一句代码在做什么,单片机的本质其实就是在操作寄存器,让单片机完成我们想要的动作例如点亮一个LED灯,stm32的库函数开发也不例外它只不过是将操作寄存器封装成一个个函数,我们只要配置指定函数的参数,再调用该函数自动把对应的寄存器配置好,其实本质还是操作寄存器,更加方便快捷,如果你只学库函数的话后期就有种空中阁楼的感觉,知其然不知其所以然。
总结:寄存器必须要学,如果前期学的有点困难,可以学一段时间库函数在返回看寄存器会好很多;
目录
1.6.5 结构体在STM32单片机程序开发中如何发挥作用?(深刻理解)
一、C 语言基础知识复习
本节主要复习一下 C 语言基础知识,对于 C 语言比较熟练的读者,可以跳过此节, 对于基础比较薄弱的读者,建议好好学习一下本节内容。 由于 C 语言博大精深,不可能我们一小节就全讲明白了,所以本节我们只是复习 STM32 开发时常用的几个 C 语言知识点,以便大家的更好的学习并编写 STM32 代码。
1.0 stdint.h
stdint.h是从C语言的标准C99中引进的一个标准C库的文件,里面主要是基本的数据类型进行重命名,要在Keil5中应用命名后的数据类型名,必须要在keil5中进行配置,这个头文件的在安装包的路径为:C:\Keil-v5\ARM\ARMCC\include。


1.1 位操作
C 语言位操作相信学过 C 语言的人都不陌生了,简而言之,就是对基本类型变量可以在位级别进行操作。这节的内容很多朋友都应该很熟练了,我这里也就点到为止,不深入探讨。着重讲解位操作在单片机开发中的一些实用技巧。C 语言支持如下 6 种位操作:


应用场景1:在不改变其他位的值的状况下,对某几个位进行设值。这个场景在单片机开发中经常使用,方法就是:先对需要设置的位用&操作符进行清零操作, 然后用|操作符设值。
比如我要改变 GPIOA 的 CRL 寄存器 bit6(第 6 位)的值为 1;
//1.可以先对寄存器的值进行&清零操作:
GPIOA->CRL &= 0XFFFFFFBF; //将第 bit6 清 0 (1011 1111)
//2.然后再与需要设置的值进行|或运算:
GPIOA->CRL |= 0X00000040; //设置 bit6 的值为 1,不改变其他位的值 (0100 0000)
应用场景2:移位操作提高代码的可读性,移位操作在单片机开发中非常重要
下面是 delay_init 函数的一行代码:
SysTick->CTRL |= 1 << 1;
上面这行代码的操作就是将 CTRL 寄存器的第 1 位(从 0 开始算起)设置为 1,为什么要通过左移而不是直接设置一个固定的值呢?其实这是为了提高代码的可读性以及可重用性。 如果写成: SysTick->CTRL |= 0X0002; 这个虽然也能实现同样的效果,但是可读性稍差,而且修改也比较麻烦。
应用场景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 位了, 可读性非常强。
应用场景4:^按位异或操作使用技巧,该功能非常适合用于控制某个位翻转,常见的应用场景就是控制 LED 闪烁
GPIOB->ODR ^= 1 << 5;
//1的补码(十六进制显示):0000 0000 0000 0001
//左移5位 0000 0000 0010 0000
//二者进行异或 0000 0000 0010 0001
执行一次该代码,就会使 PB5 的输出状态翻转一次,如果我们的 LED 接在 PB5 上,就可 以看到 LED 闪烁了。

1.2 #define宏定义
define 是 C 语言中的预处理命令,它用于宏定义,可以提高源代码的可读性,为编程提供方 便。常见的格式: #define 标识符 字符串 “标识符”为所定义的宏名。“字符串”可以是常数、表达式、格式串等。
例如: #define HSE_VALUE 8000000U
#define PLL_M 8 //定义标识符 PLL_M 的值为 8。
定义标识符 HSE_VALUE 的值为 8000000,数字后的 U 表示 unsigned 的意思。 至于 define 宏定义的其他一些知识,比如宏定义带参数这里我们就不多讲解。
1.3 ifdef条件编译
单片机程序开发过程中,经常会遇到一种情况,当满足某条件时对一组语句进行编译,而 当条件不满足时则编译另一组语句。常见指令如下所示:

条件编译命令最常见的形式为:
#ifdef 标识符
程序段 1
#else
程序段 2
#endif
它的作用是:当标识符已经被定义过(一般是用#define 命令定义),则对程序段 1 进行编译, 否则编译程序段 2。 其中#else 部分也可以没有 ,即:
#ifdef
程序段 1
#endif
条件编译在 MDK 里面是用得很多,在自己编写的头文件中经常会看到这样的语句,它的作用是防止头文件重复引用。
#ifndef _XXX_H_
#define _XXX_H_
//代码
#endif
条件编译也是 C 语言的基础知识, 这里也就点到为止吧。
1.4 extern外部声明
C 语言中 extern 可以置于变量或者函数前,以表示变量或者函数的定义在别的文件中,提示编 译器遇到此变量和函数时在其他模块中寻找其定义。这里面要注意,对于 extern 申明变量可以多次,但定义只有一次。在我们的代码中你会看到看到这样的语句:
extern uint16_t g_usart_rx_sta; //声明变量
extern void delay_us(uint32_t nus); //声明函数
这个语句是申明 g_usart_rx_sta 变量在其他文件中已经定义了,在这里要使用到。所以,你 肯定可以找到在某个地方有变量定义的语句: uint16_t g_usart_rx_sta; extern 的使用比较简单,但是也会经常用到,需要掌握。
1.5 typedef类型重命名(重点理解 )
typedef 用于为现有类型创建一个新的名字,或称为类型别名,用来简化变量的定义。typedef 在 MDK 用得最多的就是定义结构体的类型别名和枚举类型了。
最基本的用法如下:
typedef unsigned char uint8_t;
typedef unsigned short int uint16_t;
typedef unsigned int uint32_t;
struct _GPIO
{
__IO uint32_t MODER;
__IO uint32_t OTYPER;
__IO uint32_t CRL;
__IO uint32_t CRH;
…
};
定义了一个结构体 GPIO,这样我们定义结构体变量的方式为:
struct _GPIO gpiox; //定义结构体变量 gpiox
但是这样很繁琐,MDK 中有很多这样的结构体变量需要定义。这里我们可以为结体定义一个别名 GPIO_TypeDef,这样我们就可以在其他地方通过别名 GPIO_TypeDef 来定义结构体变 量了,方法如下:
typedef struct _GPIO
{
__IO uint32_t MODER;
__IO uint32_t OTYPER;
__IO uint32_t CRL;
__IO uint32_t CRH;
…
} GPIO_TypeDef;
上述引入typedef对结构体重命名,相当于下面这行代码
typedef struct _GPIO GPIO_TypeDef
Typedef 为结构体定义一个别名 GPIO_TypeDef,这样我们可以通过 GPIO_TypeDef 来定义 结构体变量: 这里的 GPIO_TypeDef 就跟 struct _GPIO 是等同的作用了,但是 GPIO_TypeDef 使用起来方便很多。
GPIO_TypeDef gpiox;
1.6 结构体(重点理解)
经常很多用户提到,他们对结构体使用不是很熟悉,但是 MDK 中太多地方使用结构体以及 结构体指针,这让他们一下子摸不着头脑,学习 STM32 的积极性大大降低,其实结构体并不是那么复杂,这里我们稍微提一下结构体的一些知识,还有一些知识我们会在下面的“寄存器映射”中讲到一些,结构体的理解与运用对于STM32是非常重要的!!!
1.6.1 概念
由若干基本数据类型集合组成的一种自定义数据类型,也叫聚合类型
1.6.2 结构体的定义
struct 结构体名
{
成员列表;
} 变量名列表(可选);
//C语言中的简单应用,如下定义学生类型的结构体
struct student
{
char *name; //姓名
int num; //学号
int age; //年龄
char group; //所在学习小组
float score; //成绩
}stu1, stu2;
struct student stu3,stu4; //定义结构体变量,赋初值
stu3.name = "张三";
stu3.num = 1;
stu3.age = 18;
stu3.group = 'A';
stu3.score = 80.9;
1.6.3 结构体成员变量的访问
struct U_TYPE
{
int BaudRate
int WordLength;
}usart1, usart2;
struct U_TYPE *usart3; // 定义结构体指针变量 usart3
方式1:通过结构体成员变量 . 访问。
方式2:通过结构体指针的指向符->访问。(STM32常用的方式)
usart1.BaudRate; //结构体变量访问
usart3->BaudRate; //结构体指针变量访问
1.6.4 结构体传参
要想在函数内部修改外部结构体的成员变量的值,必须要传结构体的地址,也就是函数设计的参数为结构体指针,函数调用时要传入结构体的地址!!!
1.6.5 结构体在STM32单片机程序开发中如何发挥作用?(深刻理解)
在我们单片机程序开发
STM32基础知识入门:寄存器与映射

最低0.47元/天 解锁文章
2060

被折叠的 条评论
为什么被折叠?



