嵌入式开发四:STM32 基础知识入门

STM32基础知识入门:寄存器与映射

       为方便更好的学习STM32单片机,本篇博客主要总结STM32的入门基础知识,重点在于理解寄存器以及存储器映射和寄存器映射,深刻体会STM32是如何组织和管理庞大的寄存器,从而提高开发效率的,为后面的基于标准库的开发做好铺垫,务必认真学习,深刻理解。俗话说得好地基不牢地动山摇,要想学好单片机必须要非常熟悉底原理,毕竟我们这是底层开发所以接触到越底层越好,能让我们知道每一句代码在做什么,单片机的本质其实就是在操作寄存器,让单片机完成我们想要的动作例如点亮一个LED灯,stm32的库函数开发也不例外它只不过是将操作寄存器封装成一个个函数,我们只要配置指定函数的参数,再调用该函数自动把对应的寄存器配置好,其实本质还是操作寄存器,更加方便快捷,如果你只学库函数的话后期就有种空中阁楼的感觉,知其然不知其所以然。

总结:寄存器必须要学,如果前期学的有点困难,可以学一段时间库函数在返回看寄存器会好很多;

目录

一、C 语言基础知识复习

1.0 stdint.h

1.1 位操作

1.2 #define宏定义

1.3 ifdef条件编译

1.4 extern外部声明

1.5 typedef类型重命名(重点理解 )

1.6 结构体(重点理解)

1.6.1 概念

1.6.2 结构体的定义

1.6.3 结构体成员变量的访问

1.6.4 结构体传参

 1.6.5 结构体在STM32单片机程序开发中如何发挥作用?(深刻理解)

1.7 指针(重点理解 )

1.8 嵌入式单片机C代码规范与风格

二、 寄存器基础知识(重点理解 )

2.1 什么是寄存器及作用?

2.2 寄存器的分类

2.3 寄存器应用举例 

三、 STM32F407 系统架构(重点理解 )

3.1 Cortex M4内核&芯片

3.2 STM32系统架构

四、存储器映射(重点理解 )

4.1 STM32单片机的寻址范围

4.2  存储器映射

​4.3 存储器区域功能划分

4.3.1 存储器 Block0 内部区域功能划分

4.3.2 存储器 Block1 内部区域功能划分

4.3.3 存储器 Block2 内部区域功能划分

五、寄存器映射(重点理解 )

5.1 什么是寄存器映射,简单举例

5.2 寄存器地址的计算(必须会)

5.2.1 总线基地址

5.2.2 外设基地址

5.2.3 外设寄存器的地址

5.3 寄存器描述解读

5.4 外设寄存器解读

5.5 C 语言对寄存器的封装

5.5 总结

5.6 举例


一、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单片机程序开发中如何发挥作用?(深刻理解)

          在我们单片机程序开发

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

未来可期,静待花开~

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值