总线的组成部分有哪些_总线的组成部分有哪些

总线的组成部分有哪些_总线的组成部分有哪些基础知识一、单片机下C语法的使用技巧(1)位操作(2)define宏定义(3)ifdef条件编译(4)extern引用外部变量申明(5)typedef类型别名(6)结构体struct二、STM

一、GPIO输入输出各种模式

链接: GPIO输入输出各种模式详解.

二、STM32F407总线架构

在这里插入图片描述
主系统由 32 位多层 AHB 总线矩阵构成。总线矩阵用于主控总线之间的访问仲裁管理。仲裁采
取循环调度算法。总线矩阵可实现以下部分互联:

八条主控总线是:
Cortex-M4 内核 I 总线, D 总线和 S 总线;
DMA1 存储器总线, DMA2 存储器总线;
DMA2 外设总线;
以太网 DMA 总线;
USB OTG HS DMA 总线;
七条被控总线:
内部 FLASH ICode 总线;
内部 FLASH DCode 总线;
主要内部 SRAM1(112KB)
辅助内部 SRAM2(16KB);
辅助内部 SRAM3(64KB) (仅适用 STM32F42xx 和 STM32F43xx 系列器件);
AHB1 外设 和 AHB2 外设;
FSMC

下面我们具体讲解一下图中几个总线的知识。

  • I 总线(S0):此总线用于将 Cortex-M4 内核的指令总线连接到总线矩阵。内核通过此总
    线获取指令。此总线访问的对象是包括代码的存储器。
  • D 总线(S1):此总线用于将 Cortex-M4 数据总线和 64KB CCM 数据 RAM 连接到总线矩
    阵。内核通过此总线进行立即数加载和调试访问。
  • S 总线(S2):此总线用于将 Cortex-M4 内核的系统总线连接到总线矩阵。此总线用于访
    问位于外设或 SRAM 中的数据。
  • DMA 存储器总线(S3,S4):此总线用于将 DMA 存储器总线主接口连接到总线矩阵。
    DMA 通过此总线来执行存储器数据的传入和传出。
  • DMA 外设总线:此总线用于将 DMA 外设主总线接口连接到总线矩阵。DMA 通过此
    总线访问 AHB 外设或执行存储器之间的数据传输。
  • 以太网 DMA 总线:此总线用于将以太网 DMA 主接口连接到总线矩阵。以太网 DMA
    通过此总线向存储器存取数据。
  • USB OTG HS DMA 总线(S7):此总线用于将 USB OTG HS DMA 主接口连接到总线矩
    阵。USB OTG HS DMA 通过此总线向存储器加载/存储数据。

三、STM32F407时钟系统

(1) STM32F4 时钟树概述

众所周知,时钟系统是 CPU 的脉搏,就像人的心跳一样。所以时钟系统的重要性就不言而喻了。
一般stm32所有片上外设使用之前,一定要进行时钟RCC配置,并使能时钟。

  • 为什么 STM32F4 要有多个时钟源呢?
    STM32F4 的时钟系统比较复杂,因为首先 STM32F4 本身就非常复杂,外设非常的多,但是并不是所有外设都需要像系统时钟这么高的频率,不同片上外设需要的时钟频率不同,比如看门狗以及 RTC 只需要几十 k 的时钟即可。
    同一个电路,时钟越快 / 时钟频率越高 功耗越大,同时抗电磁干扰能力也会越弱,所以对于较为复杂的 MCU 一般都是采取多时钟源的方法来解决这些问题。

(1)STM32F4 的时钟系统图
在 STM32F4 中,有 5 个最重要的时钟源:HSI、HSE、LSI、LSE、PLL
1)PLL锁相环(PhaseLockedLoop)实际是分为两个时钟源,分别为主 PLL 和专用 PLL。
锁相环是一种反馈电路,利用外部输入的参考信号控制环路内部振荡信号的频率和相位,其作用是使得电路上的时钟和某一外部时钟的相位同步。

2)从时钟频率来分可以分为高速时钟源和低速时钟源。HSI,HSE 以及 PLL 是高速时钟,LSI 和 LSE 是低速时钟。

3)从时钟信号来源可分为外部时钟源和内部时钟源,外部时钟源就是从外部通过接晶振的方式获取时钟源,其中 HSE 和LSE 是外部时钟源,其他的是内部时钟源。

在这里插入图片描述
在这里插入图片描述
OSC_OUT/IN:外接外部晶体振荡器,提供精确时钟源;
在这里插入图片描述
MCOM1/2:是把系统内部时钟输出到片外使用;

下面我们看看 STM32F4 的这 5 个时钟源,按图中红圈标示的顺序:
RC振荡器:输出频率不是很稳定,看门狗WDG对时钟频率精度要求不是很高;
RTC时钟来源一般是外部时钟外接晶体振荡器 = 32.768KHZ,输出频率比较稳定、精度较高;

  • ① LSI 是低速内部时钟,RC 振荡器,频率为 32kHz 左右。供独立看门狗和自动唤醒单元使用。
  • ② LSE 是低速外部时钟,接频率为 32.768kHz 的石英晶体。这个主要是 RTC 的时钟源。
  • ③ HSE 是高速外部时钟,可接石英/陶瓷谐振器或者接外部时钟源,频率范围为 4MHz~26MHz。
    STM32F407开发板接的是 8M 的晶振。HSE 也可以直接做为系统时钟或者 PLL 输入
  • ④ HSI 是高速内部时钟,RC 振荡器,频率为 16MHz。可以直接作为系统时钟或者用作 PLL
    输入。
  • ⑤ PLL 为锁相环倍频输出。STM32F4 有两个 PLL:
    1) 主 PLL(PLL)由 HSE 或者 HSI 提供时钟信号,并具有两个不同的输出时钟。
    第一个输出 PLLP 用于生成高速的系统时钟(最高 168MHz)
    第二个输出 PLLQ 用于生成 USB OTG FS 的时钟(48MHz),随机数发生器的时钟和 SDIO
    时钟。
    2)专用 PLL(PLLI2S)用于生成精确时钟,从而在 I2S 接口实现高品质音频性能。

(2)主 PLL 时钟第一个高速时钟输出 PLLP 的计算方法
STM32F4 主 PLL 时钟图
STM32F4 主 PLL 时钟图

  • 主 PLL 时钟的时钟源要先经过一个分频系数为 M 的分频器,然后经过倍频系数为 N 的倍频器出来之后的时候还需要经过一个分频系数为 P(第一个输出 PLLP)或者 Q(第二个输出 PLLQ)的分频器分频之后,最后才生成最终的主 PLL 时钟。

  • eg:如果我们选择HSE为PLL时钟源,同时SYSCLK时钟源为PLL,那么SYSCLK时钟为 168MHz。
    配置时钟:外部晶振选择 8MHz。设置相应的分频器 M=8,倍频器倍频系数 N=336,分频器分频系数 P=2,那么主 PLL 生成的第一个输出高速时钟 PLLP 为:

	PLL=8MHz * N/ (M*P)=8MHz* 336 /(8*2) = 168MHz

(3) 5 个时钟源是怎么给各个外设以及系统提供时钟的呢?
根据时钟系统图中 A~G 标示:

  • A:看门狗时钟输入。从图中可以看出,看门狗时钟源只能是低速的 LSI 时钟(低速内部时钟)

  • B:RTC 时钟源,从图上可以看出,RTC 的时钟源可以选择 LSI,LSE,以及HSE 分频后的时钟,HSE 分频系数为 2~31。

  • C: STM32F4 输出时钟 MCO1 和 MCO2
    1)MCO1 是向芯片的 PA8 引脚输出时钟。有四个时钟来源分别为:HSI,LSE,HSE 和 PLL 时钟。
    2)MCO2 是向芯片的PC9 输出时钟,同样有四个时钟来源分别为:HSE,PLL,SYSCLK 以及 PLLI2S
    时钟。
    注意:MCO 输出时钟频率最大不超过 100MHz。

  • D:系统时钟。从图可以看出,SYSCLK 系统时钟来源有三个方面:HSI、HSE 和 PLL。
    在我们实际应用中,因为对时钟速度要求都比较高,对于 STM32F4 这种级别的处理器,一般情况下,都是采用 PLL 作为 SYSCLK时钟源
    系统时钟是片上大部分外设的直接或间接的时钟来源。

  • E:以太网 PTP 时钟,AHB 时钟,APB2 高速时钟,APB1 低速时钟。这些时钟都是来源于 SYSCLK 系统时钟,其中以太网 PTP 时钟是直接使用系统时钟。AHB、APB2 和 APB1 时钟是经过 SYSCLK 时钟分频得来。
    这里记住:AHB最大时钟为168MHz,APB2高速时钟最大频率为84MHz,而APB1低速时钟最大频
    率为 42MHz。

  • F: I2S 时钟源。从图可以看出,I2S 的时钟源来源于 PLLI2S 或者映射到 I2S_CKIN 引脚的外部时钟。I2S 出于音质的考虑,对时钟精度要求很高。探索者 STM32F4 开发板使用的是内部 PLLI2SCLK

  • G: STM32F4 内部以太网 MAC 时钟的来源。对于 MII 接口来说,必须向外部PHY 芯片提供 25Mhz 的时钟,这个时钟,可以由 PHY 芯片外接晶振,或者使用STM32F4 的 MCO 输出来提供。然后,PHY 芯片再给 STM32F4 提供ETH_MII_TX_CLK 和 ETH_MII_RX_CLK 时钟。
    对于 RMII 接口来说,外部必须提供 50Mhz 的时钟驱动 PHY 和 STM32F4 的 ETH_RMII_REF_CLK,这个 50Mhz时钟可以来自 PHY、有源晶振或者 STM32F4 的 MCO。我们的开发板使用的是RMII 接 口 , 使 用 PHY 芯 片 提 供 50Mhz 时 钟 驱 动 STM32F4 的ETH_RMII_REF_CLK。

  • H:外部 PHY(物理接口层) 提供的 USB OTG HS(60MHZ)时钟

注意:
(1)Cortex 系统定时器 Systick 的时钟源可以是 AHB 时钟 HCLK 或HCLK 的 8 分频。具体配置请参考 Systick 定时器配置。
(2)在以上的时钟输出中,有很多是带使能控制的,例如 AHB 总线时钟、内核时钟、各种 APB1外设、APB2 外设等等。当需要使用某模块时,记得一定要先使能对应的时钟

(2)STM32F4 时钟初始化配置

TM32F4 时钟系统初始化是在 system_stm32f4xx.c 中的 SystemInit()函数中完成的。
对于系统时钟关键寄存器的设置主要是在 SystemInit 函数中调用 SetSysClock()函数来设置的。

1)我们可以先看看 SystemInit ()函数体:

void SystemInit(void)
{ 
   
	/* FPU settings ------------------------------------------------------------*/
	#if (__FPU_PRESENT == 1) && (__FPU_USED == 1)
	SCB->CPACR |= ((3UL << 10*2)|(3UL << 11*2)); /* set CP10 and CP11 Full Access */
	#endif
	/* Reset the RCC clock configuration to the default reset state ------------*/
	/* Set HSION bit */
	RCC->CR |= (uint32_t)0x00000001;
	/* Reset CFGR register */
	RCC->CFGR = 0x00000000;
	/* Reset HSEON, CSSON and PLLON bits */
	RCC->CR &= (uint32_t)0xFEF6FFFF;
	/* Reset PLLCFGR register */
	RCC->PLLCFGR = 0x24003010;
	/* Reset HSEBYP bit */
	RCC->CR &= (uint32_t)0xFFFBFFFF;
	/* Disable all interrupts */
	RCC->CIR = 0x00000000;
	#if defined (DATA_IN_ExtSRAM) || defined (DATA_IN_ExtSDRAM)
	SystemInit_ExtMemCtl();
	#endif /* DATA_IN_ExtSRAM || DATA_IN_ExtSDRAM */
	/* Configure the System clock source, PLL Multiplier and Divider factors, AHB/APBx prescalers and Flash settings ----------------------------------*/
	SetSysClock();
	/* Configure the Vector Table location add offset address ------------------*/
	#ifdef VECT_TAB_SRAM
	SCB->VTOR = SRAM_BASE | VECT_TAB_OFFSET; /* Vector Table Relocation in Internal SRAM */
	#else
	SCB->VTOR = FLASH_BASE | VECT_TAB_OFFSET; /* Vector Table Relocation in Internal FLASH */
	#endif
}
  • SystemInit() 函数:开始先进行浮点运算单元设置,然后是复位 PLLCFGR、CFGR 寄存器,同时
    通过设置 CR 寄存器的 HSI 时钟使能位来打开 HSI 时钟。默认情况下如果 CFGR 寄存器复位,
    那么是选择 HSI 作为系统时钟,这点大家可以查看 RCC->CFGR 寄存器的位描述最低 2 位可以得知,当低两位配置为 00 的时候(复位之后),会选择 HSI 振荡器为系统时钟。
    也就是说,调用 SystemInit() 函数之后,首先是选择 HSI 作为系统时钟。

  • SetSysClock()函数:先使能外部时钟 HSE,等待 HSE 稳定之后,配置AHB,APB1,APB2 时钟相关的分频因子,也就是相关外设的时钟。等待这些都配置完成之后,打开主 PLL 时钟,然后设置主 PLL 作为系统时钟 SYSCLK 时钟源。如果 HSE 不能达到就绪状态(比如外部晶振不能稳定或者没有外部晶振),那么依然会是 HSI 作为系统时钟。

2)在设置主 PLL 时钟的时候,会要设置一系列的分频系数和倍频系数参数。大家可以从 SetSysClock 函数的这行代码看出:

RCC->PLLCFGR = PLL_M | (PLL_N << 6) | (((PLL_P >> 1) -1) << 16) |
(RCC_PLLCFGR_PLLSRC_HSE) | (PLL_Q << 24);

这些参数是通过宏定义标识符的值来设置的。默认的配置在 System_stm32f4xx.c 文件开头的地方配置。对于我们开发板,我们的设置参数值如下:

#define PLL_M 8
#define PLL_Q 7
#define PLL_N 336
#define PLL_P 2

所以我们的主 PLL 时钟为:

PLL=8MHz * N/ (M*P)=8MHz* 336 /(8*2) = 168MHz

3)在开发过程中,我们可以通过调整这些值来设置我们的系统时钟。这里还有个特别需要注意的地方,就是我们还要同步修改 stm32f4xx.h 中宏定义标识符HSE_VALUE 的值为我们的外部时钟:

#if !defined (HSE_VALUE)
#define HSE_VALUE ((uint32_t)8000000) /*!< Value of the External oscillator in Hz */
#endif /* HSE_VALUE */

这里默认固件库配置的是 25000000,我们外部时钟为 8MHz,所以我们根据我们硬件情况修改为 8000000 即可。

注意:在 system_stm32f4xx.c 中的 SystemInit()函数中设置主 PLL 作为系统时钟 SYSCLK 时钟源时,还需要修改 stm32f4xx.h 中宏定义标识符HSE_VALUE 的值设置外部时钟。

4)SystemInit 是整个设置系统时钟的入口函数。这个函数对于我们使用 ST提供的 STM32F4 固件库的话,会在系统启动之后先执行 main 函数,然后再接着执行 SystemInit函数实现系统相关时钟的设置。这个过程设置是在启动文件 startup_stm32f40_41xxx.s 中间设置的,启动文件中这段启动代码:

; Reset handler
Reset_Handler PROC
EXPORT Reset_Handler [WEAK]
IMPORT SystemInit
IMPORT __main
LDR R0, =SystemInit
BLX R0
LDR R0, =__main
BX R0
ENDP

这段代码的作用是:在系统复位之后引导进入 main 函数,同时在进入 main 函数之前,首先要调用 SystemInit() 系统初始化函数完成系统时钟等相关配置。

  • 最后我们总结一下 SystemInit()函数中设置的系统时钟大小:
SYSCLK(系统时钟) = 168MHz
AHB 总线时钟(HCLK=SYSCLK) =	168MHz
APB1 总线时钟(PCLK1=SYSCLK/4) =	42MHz
APB2 总线时钟(PCLK2=SYSCLK/2) =	84MHz
PLL 主时钟 =	168MHz

(3)STM32F4 时钟使能和配置:RCC_函数

上面讲了系统复位之后,调用 SystemInit() 函数后完成了相关时钟的默认初始化配置,那么如果在系统初始化之后,我们还需要修改某些时钟源配置或者说还需要使能相关的外设时钟该怎么设置呢?

RCC(Reset Clock Controller) —— 复位与时钟控制

通过 STM32F4 标准固件库配置方法,配置 RCC 相关寄存器
在 STM32F4 标准固件库里,时钟源的选择和时钟使能等函数都是在 RCC 相关固件库文件 stm32f4xx_rcc.h 和 stm32f4xx_rcc.c 中声明和定义。打开 stm32f4xx_rcc.h 文件可以看到文件开头有很多宏定义标识符,然后是一系列时钟配置和时钟使能函数申明。这些函数大致可以归结为三类:

  • 外设时钟使能函数
  • 时钟源配置选择和分频因子配置函数
  • 外设复位函数

当然还有几个获取时钟源配置的函数。下面我们以几种常见的操作来简要介绍一下这些库函数的使用。

(1)时钟使能函数

时钟使能相关函数:包括外设时钟设置使能时钟源使能两类。

(1)外设时钟使能函数

void RCC_AHB1PeriphClockCmd(uint32_t RCC_AHB1Periph, FunctionalState NewState);
void RCC_AHB2PeriphClockCmd(uint32_t RCC_AHB2Periph, FunctionalState NewState);
void RCC_AHB3PeriphClockCmd(uint32_t RCC_AHB3Periph, FunctionalState NewState);
void RCC_APB1PeriphClockCmd(uint32_t RCC_APB1Periph, FunctionalState NewState);
void RCC_APB2PeriphClockCmd(uint32_t RCC_APB2Periph, FunctionalState NewState);

AHB(Advanced High performance Bus):系统总线
APB(Advanced Peripheral Bus): 外围总线

5 个函数分别用来使能 5 个总线下面挂载的外设时钟,这些总线分别为:AHB1 总线,AHB2 总线,AHB3 总线,APB1 总线和 APB2 总线。要使能某个外设,调用对应的总线外设时钟使能函数即可

STM32F4 的外设在使用之前,必须对时钟进行使能,如果没有使能时钟,那么外设是无法正常工作的。对于哪个外设是挂载在哪个总线之下,虽然我们也可以查手册查询到,但是这里如果大家使用的是
库函数的话,实际上是没有必要去查询手册的,这里我们给大家介绍一个小技巧:

eg:我们要使能 GPIOA,我们只需要在 stm32f4xx_rcc.h 头文件里面搜索 GPIOA,就可以搜索到对应的时钟使能函数的第一个入口参数为:RCC_AHB1Periph_GPIOA,从这个宏定义标识符一眼就可以看出,GPIOA 是挂载在 AHB1 下面。同理,对于串口 1 我们可以搜索 USART1,找到标识符为 RCC_APB2Periph_USART1,那么很容易知道串口 1 是挂载在 APB2 之下。

注:
(1)USART:(Universal Synchronous/Asynchronous Receiver/Transmitter)通用同步/异步串行接收/发送器,USART是一个全双工通用同步/异步串行收发模块,该接口是一个高度灵活的串行通信设备,兼容UART。
(2)USART收发模块分为三大部分:时钟发生器、数据发送器和接收器。控制寄存器为所有模块共享。

  • 时钟发生器:由同步逻辑电路(在同步从模式下由外部时钟输入驱动)和波特率发生器组成。发送时钟引脚XCK仅用于同步发送模式下

  • 发送器部分由一个单独的写入缓冲器(发送UDR)、一个串行移位寄存器、校验位发生器和用于处理不同帧结构的控制逻辑电路构成。使用写入缓冲器,实现了连续发送多帧数据无延时的通信。

  • 接收器是USART模块最复杂的部分,最主要的是时钟和数据接收单元。数据接收单元用作异步数据的接收。除了接收单元,接收器还包括校验位校验器、控制逻辑、移位寄存器和两级接收缓冲器(接收UDR)。接收器支持与发送器相同的帧结构,同时支持帧错误、数据溢出和校验错误的检测。

(2)时钟源使能函数
前面我们已经讲解过 STM32F4 有 5 大类时钟源。这里我们列出来几种重要的时钟源使能函数:

void RCC_HSICmd(FunctionalState NewState);
void RCC_LSICmd(FunctionalState NewState);
void RCC_PLLCmd(FunctionalState NewState);
void RCC_PLLI2SCmd(FunctionalState NewState);
void RCC_PLLSAICmd(FunctionalState NewState);
void RCC_RTCCLKCmd(FunctionalState NewState);

(2)时钟源配置选择和分频因子配置函数

时钟源选择和分频因子配置函数是用来选择相应的时钟源配置相应的时钟分频系数

我们之前讲解过系统时钟SYSCLK,我们可以选择 HSI、HSE 和 PLL 中的一个时钟源为系统时钟。
那么到底选择哪一个,这是可以配置的。

(1)时钟源配置函数

void RCC_LSEConfig(uint8_t RCC_LSE);
void RCC_SYSCLKConfig(uint32_t RCC_SYSCLKSource);
void RCC_HCLKConfig(uint32_t RCC_SYSCLK);
void RCC_PCLK1Config(uint32_t RCC_HCLK);
void RCC_PCLK2Config(uint32_t RCC_HCLK);
void RCC_RTCCLKConfig(uint32_t RCC_RTCCLKSource);
void RCC_PLLConfig(uint32_t RCC_PLLSource, uint32_t PLLM,uint32_t PLLN, 
															uint32_t PLLP, uint32_t PLLQ);

eg:我们要设置系统时钟源为 HSI,那么我们可以调用系统时钟源配置函数:

void RCC_HCLKConfig(uint32_t RCC_SYSCLK);

具体配置方法如下:

RCC_HCLKConfig(RCC_SYSCLKSource_HSI);//配置时钟源为 HSI

又如我们要设置 APB1 总线时钟为 HCLK 的 2 分频,也就是设置分频因子为 2 分频,那么如果我们要使能 HSI 调用的函数为:

void RCC_PCLK1Config(uint32_t RCC_HCLK);

具体配置方法如下:

RCC_PCLK1Config(RCC_HCLK_Div2);

(3)外设复位函数

外设复位函数:

void RCC_AHB1PeriphResetCmd(uint32_t RCC_AHB1Periph, FunctionalState NewState);
void RCC_AHB2PeriphResetCmd(uint32_t RCC_AHB2Periph, FunctionalState NewState);
void RCC_AHB3PeriphResetCmd(uint32_t RCC_AHB3Periph, FunctionalState NewState);
void RCC_APB1PeriphResetCmd(uint32_t RCC_APB1Periph, FunctionalState NewState);
void RCC_APB2PeriphResetCmd(uint32_t RCC_APB2Periph, FunctionalState NewState);

这类函数跟前面的外设时钟使能函数使用方法基本一致,不同的是一个是用来使能外设时钟,
一个是用来复位对应的外设。在调用函数的时候一定不要混淆。

五、NVIC 中断优先级管理

NVIC:内嵌向量中断控制器(Nested Vectored Interrupt Controller)提高了实时性。
NVIC和处理器内核紧密相连,提供中断控制器,用于总体管理异常:

  • 支持嵌套和向量中断
  • 自动保存和恢复处理器状态
  • 动态改变优先级
  • 简化的和确定的中断时间

(1)支持的中断

Cortex-M4 内核支持 256 个中断,其中包含了 16 个内核中断和 240 个外部中断,并且具有 256 级的可编程中断设置。但是STM32F4 并没有使用 Cortex-M4 内核的全部东西,只是用了它的一部分。

STM32F40xx/STM32F41xx 总共有 92 个中断。92 个中断里面:包括 10 个内核中断和 82 个可屏蔽中断,具有 16 级可编程的中断优先级,而我们常用的就是这 82 个可屏蔽中断。在 MDK 内,与 NVIC相关的寄存器,MDK 为其定义了如下的结构体:

typedef struct
{ 
   
	__IO uint32_t ISER[8]; /*!< Interrupt Set Enable Register */
	uint32_t RESERVED0[24];
	__IO uint32_t ICER[8]; /*!< Interrupt Clear Enable Register */
	uint32_t RSERVED1[24];
	__IO uint32_t ISPR[8]; /*!< Interrupt Set Pending Register */
	uint32_t RESERVED2[24];
	__IO uint32_t ICPR[8]; /*!< Interrupt Clear Pending Register */
	uint32_t RESERVED3[24];
	__IO uint32_t IABR[8]; /*!< Interrupt Active bit Register */
	uint32_t RESERVED4[56];
	__IO uint8_t IP[240]; /*!< Interrupt Priority Register, 8Bit wide */
	uint32_t RESERVED5[644];
	__O uint32_t STIR; /*!< Software Trigger Interrupt Register */
} NVIC_Type;

STM32F4 的中断在这些寄存器的控制下保持着有序的执行。只有了解这些中断寄存器,才能真正会使用 STM32F4 的中断。下面重点介绍这几个寄存器:

  • ISER[8] :Interrupt Set-Enable Registers,是一个中断使能寄存器组。
    (1) CM4 内核支持 256 个中断,用 8 个 32 位寄存器来控制,每个位控制一个中断(256 = 8 x 32)
    但是STM32F4 可屏蔽的中断最多只有 82 个,所以有用的寄存器是三个ISER[0 ~ 2] = 3 x 32 = 96,
    总共可以表示 96 个中断,而 STM32F407 只用了其中的前 82 个中断,还剩下14个。
    (2)82个中断对应位:
    ISER[0] 的 bit0 ~ 31 分别对应中断0 ~ 31;
    ISER[1]的 bit0 ~ 32 对应中断 32 ~ 63;
    ISER[2]的 bit0 ~ 17 对应中断 64~81;
    (3)想要使能某个中断,必须将相应的 ISER 对应位置 1,使该中断被使能,这里仅仅是使能,还要配合中断分组、屏蔽、IO 口映射等设置才算是一个完整的中断设置。具体每一位对应哪个中断,请参考 stm32f4xx.h 里面的第 188 行处。

  • ICER[8] :Interrupt Clear-Enable Registers,是一个中断除能寄存器组。该寄存器组与 ISER 的作用恰好相反是用来清除某个中断的使能的。其对应位的功能也和 ICER 一样。
    注意:这里要专门设置一个 ICER 来清除中断位,而不是向 ISER 写 0 来清除,是因为 NVIC 的这些寄
    存器都是写 1 有效的,写 0 是无效的。

  • ISPR[8]:Interrupt Set-Pending Registers,是一个中断挂起控制寄存器组。每个位对应的中断和 ISER 是一样的。通过置 1,可以将正在进行的中断挂起,而去执行同级或更高级别的中断。写 0 是无效的。

  • ICPR[8]:Interrupt Clear-Pending Registers,是一个中断解挂控制寄存器组。其作用与 ISPR 相反,对应位也和 ISER 是一样的。通过设置 1,可以将挂起的中断接挂。写 0 无效。

  • IABR[8]:Interrupt Active Bit Registers,是一个中断激活标志位寄存器组。对应位所代表的中断和 ISER 一样。如果为 1,则表示该位所对应的中断正在被执行。这是一个只读寄存器,通过它可以知道当前在执行的中断是哪一个,在中断执行完了由硬件自动清零。

  • IP[240]:Interrupt Priority Registers,是一个中断优先级控制寄存器组。这个寄存器组相当重要!STM32F4 的中断分组与这个寄存器组密切相关。
    IP 寄存器组由 240 个 8bit的寄存器组成,每个可屏蔽中断占用 8bit,这样总共可以表示 240 个可屏蔽中断
    而 STM32F4只用到了其中的 82 个:IP[81] ~ IP[0]分别对应中断 81~0。而每个可屏蔽中断占用的 8bit 并没有全部使用,而是只用了高 4 位,这 4 位又分为:抢占优先级和响应优先级。抢占优先级在前,响应优先级在后,而这两个优先级各占几个位又要根据 SCB->AIRCR 中的中断分组设置来决定。

(2)中断分组:抢占优先级和响应优先级

STM32F4 的中断分组:STM32F4 将中断分为 5 个组[ 0 ~ 4 ],该分组的设置是由 SCB->AIRCR 寄存器bit10 ~ 8 来定义的。具体的分配关系如下所示:

AIRCR 中断分组设置表

组	AIRCR[108] 	bit[74]分配情况 			分配结果
0	111 			04 				0 位抢占优先级,4 位响应优先级
1 	110 			13 				1 位抢占优先级,3 位响应优先级
2 	101 			22 				2 位抢占优先级,2 位响应优先级
3 	100 			31 				3 位抢占优先级,1 位响应优先级
4 	011 			40 				4 位抢占优先级,0 位响应优先级

通过这个表,我们就可以清楚的看到组 0 ~ 4 对应的配置关系
eg:组设置为 3,那么此时所有的 82 个中断,每个中断的中断优先寄存器的高四位中的最高 3 位是抢占优先级,低 1 位是响应优先级。每个中断,你可以设置抢占优先级为 0~7,响应优先级为 1 或 0。抢占优先级的级别高于响应优先级。而数值越小所代表的优先级就越高。

注意:
(1)如果两个中断的抢占优先级和响应优先级都是一样的话,则看哪个中断先发生就先执行;
(2)高优先级的抢占优先级是可以打断正在进行的低抢占优先级中断的而抢占优先级相同的中断,高优先级的响应优先级不可以打断低响应优先级的中断。

举例:
假定设置中断优先级组为 2:
然后设置中断 3(RTC_WKUP 中断)的抢占优先级为 2,响应优先级为 1;
中断 6(外部中断 0)的抢占优先级为 3,响应优先级为 0;
中断 7(外部中断 1)的抢占优先级为 2,响应优先级为 0;
那么这 3 个中断的优先级顺序为:中断 7>中断 3>中断 6。
中断 3 和中断 7 都可以打断中断 6 的中断。而中断 7 和中断 3 却不可以相互打断!

(3)库函数实现中断分组设置和中断优先级管理

(1)调用中断优先级分组函数 :NVIC_PriorityGroupConfig()

使用库函数实现以上中断分组设置以及中断优先级管理,使得以后我们的中断设置简单化。
NVIC 中断管理函数主要在 misc.c 文件里:
中断优先级分组函数 NVIC_PriorityGroupConfig(),其函数申明如下:

void NVIC_PriorityGroupConfig(uint32_t NVIC_PriorityGroup);

函数作用是对中断的优先级进行分组,这个函数在系统中只能被调用一次,一旦分组确定就不要更改

函数定义的实现过程:

void NVIC_PriorityGroupConfig(uint32_t NVIC_PriorityGroup)
{ 
   
	assert_param(IS_NVIC_PRIORITY_GROUP(NVIC_PriorityGroup));
	SCB->AIRCR = AIRCR_VECTKEY_MASK | NVIC_PriorityGroup;//设置中断优先级分组
}

从函数体可以看出,这个函数唯一目的就是通过设置 SCB->AIRCR 寄存器来设置中断优先级分组
而其入口参数“IS_NVIC_PRIORITY_GROUP”追踪到定义处可以查看到为:
其作用是用来确定分组范围为 0 – 4

#define IS_NVIC_PRIORITY_GROUP(GROUP)
(((GROUP) == NVIC_PriorityGroup_0) ||
((GROUP)  == NVIC_PriorityGroup_1) || \
((GROUP)  == NVIC_PriorityGroup_2) || \
((GROUP)  == NVIC_PriorityGroup_3) || \
((GROUP)  == NVIC_PriorityGroup_4))

eg:设置整个系统的中断优先级分组值为 2,那么方法就是直接调用此函数设置宏定义参数:
这样就确定了一共为“2 位抢占优先级,2 位响应优先级”

NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2);

(2)中断初始化函数:NVIC_Init()

设置好了系统中断分组,那么对于每个中断我们又怎么确定他的抢占优先级和响应优先级呢?
下面我们讲解一个重要的函数为中断初始化函数NVIC_Init,其函数申明为:

void NVIC_Init(NVIC_InitTypeDef* NVIC_InitStruct);

其中 NVIC_InitTypeDef 是一个结构体,结构体的成员变量:

typedef struct
{ 
   
	uint8_t NVIC_IRQChannel;
	uint8_t NVIC_IRQChannelPreemptionPriority;
	uint8_t NVIC_IRQChannelSubPriority;
	FunctionalState NVIC_IRQChannelCmd;
} NVIC_InitTypeDef;

NVIC_InitTypeDef 结构体中间有四个成员变量,成员变量的含义:

  • NVIC_IRQChannel:定义初始化的是哪个中断,可以在 stm32f10x.h 中找到每个中断对应的名字。

  • NVIC_IRQChannelPreemptionPriority:定义这个中断的抢占优先级别;

  • NVIC_IRQChannelSubPriority:定义这个中断的子优先级别,也叫响应优先级;

  • NVIC_IRQChannelCmd:该中断通道是否使能;

eg:使能串口 1 的中断,同时设置抢占优先级为 1,响应优先级位 2:

NVIC_InitTypeDef NVIC_InitStructure;;//定义一个对象
NVIC_InitStructure.NVIC_IRQChannel = USART1_IRQn;//串口 1 中断
NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority=1 ;// 抢占优先级为 1
NVIC_InitStructure.NVIC_IRQChannelSubPriority = 2;// 响应优先级位 2
NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE; //IRQ 通道使能
NVIC_Init(&NVIC_InitStructure); //根据上面指定的参数初始化 NVIC 寄存器

最后总结一下中断优先级设置的步骤:

  1. 系统运行开始的时候设置中断分组。确定组号,也就是确定抢占优先级和响应优先级的分配位数。
    调用函数为 NVIC_PriorityGroupConfig();
  2. 设置所用到的中断的中断优先级别。对每个中断调用函数为 NVIC_Init();

六、MDK 中寄存器地址名称映射分析

在51 单片机开发中,经常会引用一个 reg51.h 的头文件,通过sfr命令把地址名字和寄存器联系起来:
sfr P1 = 0x90;
sfr P1 = 0x90 :定义 P1 为 P1 端口在片内的寄存器。
然后我们往地址为 0x90 的寄存器设值的方法是:P1=value;

sfr是Special Function Register特殊功能寄存器,是80C51单片机中各功能部件对应的寄存器,用于存放相应功能部件的控制命令,状态或数据。它是80C51单片机中最具有特殊的部分sfr 是一种扩充数据类型,占用一个内存单元,值域为 0~255。利用它可以访问 51 单片机内部的所有特殊功能寄存器。

那么在 STM32 中,是否也可以这样做呢?答案是肯定的。肯定也可以通过同样的方式来做,但是 STM32 中的寄存器太多太多,如果都以这样的方式列出来,需要很大的篇幅,既不方便开发,也显得程序太杂乱无序。为了解决这种问题,引入结构体, MDK 采用的方式是通过结构体通过相同属性的对象将寄存器组织在一起。

(1) MDK 是怎么把结构体和地址对应起来的呢,为什么通过修改结构体成员变量的值就能达到操作对应寄存器的值?

  • 首先STM32F4 中的结构体和寄存器地址映射,在 stm32f4xx.h 头文件中完成的。
    查看《STM32F4 中文参考手册》可看到寄存器地址映射表。

因为地址是以字节为基本单位进行编号,STM32F4中的寄存器基本上都是 32 位,因此每个寄存器占有 4 个地址。以GPIOX组为例,每组GPIO有10个寄存器,那么就一共占用 40 个地址,地址偏移范围为0x00 ~ 0x24(0x24 = 40),这个地址偏移是相对 GPIOX 的基地址而言的。

eg:GPIOA 的基地址是怎么算出来的呢?因为 GPIO都是挂载在 AHB1 总线上的,所以它的基地址是由 AHB1 总线的基地址+GPIOA 在 AHB1总线上的偏移地址决定的。同理依次类推,我们便可以算出 GPIOA 基地址了。下面我们打开 stm32f4xx.h 定位到 GPIO_TypeDef 定义处:

typedef struct
{ 
   
  __IO uint32_t MODER;    /*!< GPIO port mode register, Address offset: 0x00 */
  __IO uint32_t OTYPER;   /*!< GPIO port output type register, Address offset: 0x04 */
  __IO uint32_t OSPEEDR;  /*!< GPIO port output speed register, Address offset: 0x08 */
  __IO uint32_t PUPDR;    /*!< GPIO port pull-up/pull-down register, Address offset: 0x0C */
  __IO uint32_t IDR;      /*!< GPIO port input data register, Address offset: 0x10 */
  __IO uint32_t ODR;      /*!< GPIO port output data register, Address offset: 0x14 */
  __IO uint16_t BSRRL;    /*!< GPIO port bit set/reset low register, Address offset: 0x18 */
  __IO uint16_t BSRRH;    /*!< GPIO port bit set/reset high register, Address offset: 0x1A */
  __IO uint32_t LCKR;     /*!< GPIO port configuration lock register, Address offset: 0x1C */
  __IO uint32_t AFR[2];   /*!< GPIO alternate function registers, Address offset: 0x20-0x24*/
} GPIO_TypeDef;

然后定位到:

#define GPIOA ((GPIO_TypeDef *) GPIOA_BASE)

#define GPIOA_BASE (AHB1PERIPH_BASE + 0x0000)

可以看出,GPIOA 就是指向的地址为 GPIOA_BASE 强制转换成 GPIO_TypeDef * 类型的指针。
这句话可以理解为:标识符 GPIOA 是一个指针,指向地址是 GPIOA_BASE 对应的内存空间,而GPIOA_BASE对应的地址空间中所存放内容的数据类型是GPIO_TypeDef 类型的。

依次类推,可以找到最顶层:

#define AHB1PERIPH_BASE (PERIPH_BASE + 0x00020000)
#define PERIPH_BASE ((uint32_t)0x40000000)

所以我们便可以算出 GPIOA 的基地址位:

GPIOA_BASE= 0x40000000 + 0x00020000 + 0x0000 = 0x40020000

固件库:GPIOA->BSRR = value;就是设置地址 0x40020000 + 0x18 (BSRR 偏移量) = 0x40020018 的寄存器 BSRR 的值。

今天的文章总线的组成部分有哪些_总线的组成部分有哪些分享到此就结束了,感谢您的阅读。

版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 举报,一经查实,本站将立刻删除。
如需转载请保留出处:https://bianchenghao.cn/85912.html

(0)
编程小号编程小号

相关推荐

发表回复

您的电子邮箱地址不会被公开。 必填项已用 * 标注