STM32F4嵌入式学习总结9

1、STM32F4IO口的复用

STM32F4 有很多的内置外设,这些外设的外部引脚都是与 GPIO 复用的。也就是说,一个 GPIO如果可以复用为内置外设的功能引脚,那么当这个 GPIO 作为内置外设使用的时候,就叫做复用。

这部分知识在《STM32F4 中文参考手册》第七章和芯片数据手册有详细的讲解哪些 GPIO 管脚是可以复用为哪些内置外设。对于本小节知识,STM32F4 中文参考手册讲解比较详细,我们同样会从中抽取重要的知识点罗列出来。同时,我们会以串口使用为例给大家讲解具体的引脚复用的配置。

STM32F4 系列微控制器 IO 引脚通过一个复用器连接到内置外设或模块。该复用器一次只允许一个外设的复用功能(AF)连接到对应的 IO 口。这样可以确保共用同一个 IO 引脚的外设之间不会发生冲突。每个 IO 引脚都有一个复用器,该复用器采用 16 路复用功能输入(AF0 到 AF15),可通过GPIOx_AFRL(针对引脚 0-7)和 GPIOx_AFRH(针对引脚 8-15)寄存器对这些输入进行配置,每四位控制一路复用:

1)完成复位后,所有 IO 都会连接到系统的复用功能 0(AF0)。

2)外设的复用功能映射到 AF1 到 AF13。

3)Cortex-M4 EVENTOUT 映射到 AF15。

STM32F4嵌入式学习总结9

接下来,我们简单说明一下这个图要如何看,举个例子,探索者 STM32F407 开发板的原理图上 PC11 的原理图如图

STM32F4嵌入式学习总结9

如上图所示,PC11 可以作为 SPI3_MISO/U3_RX/U4_RX/SDIO_D3/DCMI_D4/I2S3ext_SD等复用功能输出,这么多复用功能,如果这些外设都开启了,那么对 STM32F1 来说,那就可能乱套了,外设之间可互相干扰,但是 STM32F4,由于有复用功能选择功能,可以让 PC11 仅连接到某个特定的外设,因此不存在互相干扰的情况。

是针对引脚 0-7,对于引脚 8-15,控制寄存器为 GPIOx_AFRH。从图中可以看出。当需要使用复用功能的时候,我们配置相应的寄存器 GPIOx_AFRL 或者 GPIOx_AFRH,让对应引脚通过复用器连接到对应的复用功能外设。这里我们列出 GPIOx_AFRL 寄存器的描述,GPIOx_AFRH 的作用跟 GPIOx_AFRL 类似,只不过 GPIOx_AFRH 控制的是一组 IO 口的高八位,

STM32F4嵌入式学习总结9

从表中可以看出,32 位寄存器 GPIOx_AFRL 每四个位控制一个 IO 口,所以每个寄存器控制32/4=8 个 IO 口。寄存器对应四位的值配置决定这个 IO 映射到哪个复用功能 AF。在微控制器完成复位后,所有 IO 口都会连接到系统复用功能 0(AF0)。这里大家需要注意,对于系统复用功能 AF0,我们将 IO 口连接到 AF0 之后,还要根据所用功能进行配置

1) JTAG/SWD:在器件复位之后,会将这些功能引脚指定为专用引脚。也就是说,这些引脚在复位后默认就是 JTAG/SWD 功能。如果我们要作为 GPIO 来使用,就需要对对应的 IO口复用器进行配置。

2) RTC_REFIN:此引脚在系统复位之后要使用的话要配置为浮空输入模式。

3) MCO1 和 MCO2:这些引脚在系统复位之后要使用的话要配置为复用功能模式。对于外设复用功能的配置,除了 ADC 和 DAC 要将 IO 配置为模拟通道之外其他外设功能一律要配置为复用功能模式,这个配置是在 IO 口对应的 GPIOx_MODER 寄存器中配置的。同时要配置 GPIOx_AFRH 或者 GPIOx_AFRL 寄存器,将 IO 口通过复用器连接到所需要的复用功能对应的AFx。不是每个 IO 口都可以复用为任意复用功能外设。到底哪些 IO 可以复用为相关外设呢?这在芯片对应的数据手册(请参考光盘目录:)上面会有详细的表格列出来。对于 STM32F407,数据手册里面的 Table 9.Alternate function mapping 表格列出了所有的端口 AF 映射表,因为表格比较大,所以这里只列出 PORTA 的几个端口为例方便大家理解:

STM32F4嵌入式学习总结9

PA9 连接 AF7 可以复用为串口 1 的发送引脚 USART1_TX,PA10 连接 AF7可以复用为串口 2 的接受引脚 USART1_RX。接下来我们以串口 1 为例来讲解怎么配置 GPOPA.9,GPIOA.10 口为串口 1 复用功能。

1)首先,我们要使用 IO 复用功能外设,必须先打开对应的 IO 时钟和复用功能外设时钟。

/*使能 GPIOA 时钟*/

RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_GPIOA,ENABLE);

/*使能 USART1 时钟*/

RCC_APB2PeriphClockCmd(RCC_APB2Periph_USART1,ENABLE);

这里需要说明一下,官方库提供了五个打开 GPIO 和外设时钟的函数分别为:

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);

这五个函数分别用来打开相应的总线下 GPIO 和外设时钟。比如我们的串口 1 是挂载在APB2 总线之下,所以我们调用对应的 APB2 总线下外设时钟使能函数 RCC_APB2PeriphClockCmd来使能串口 1 时钟。对于其他外设我们调用相应的函数即可。具体库函数要怎么快速找到对应的外设使能函数,大家可以参考我们接下来的 4.7 小节快速组织代码技巧,我们有详细的举例说明。

2)其次,我们在 GIPOx_MODER 寄存器中将所需 IO(对于串口 1 是 PA9,PA10)配置为复用功能(ADC 和 DAC 设置为模拟通道)。

3)再次,我们还需要对 IO 口的其他参数,例如类型,上拉/下拉以及输出速度。上面两步,在我们库函数中是通过 GPIO_Init 函数来实现的,参考代码如下

/*GPIOA9 与 GPIOA10 初始化*/

GPIO_InitStructure.GPIO_Pin = GPIO_Pin_9 | GPIO_Pin_10;

GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF;//复用功能

GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;//速度 50MHz

GPIO_InitStructure.GPIO_OType = GPIO_OType_PP; //推挽复用输出

GPIO_InitStructure.GPIO_PuPd = GPIO_PuPd_UP; //上拉

GPIO_Init(GPIOA,&GPIO_InitStructure); //初始化 PA9,PA10

)最后,我们配置 GPIOx_AFRL 或者 GPIOx_AFRH 寄存器,将 IO 连接到所需的 AFx。这些步骤对于我们使用库函数来操作的话,是调用的 GPIO_PinAFConfig 函数来实现的。具体操作代码如下:

/*PA9 连接 AF7,复用为 USART1_TX */

GPIO_PinAFConfig(GPIOA,GPIO_PinSource9,GPIO_AF_USART1);

/* PA10 连接 AF7,复用为 USART1_RX*/

GPIO_PinAFConfig(GPIOA,GPIO_PinSource10,GPIO_AF_USART1);

对于函数 GPIO_PinAFConfig 函数,入口第一个第二个参数很好理解,可以确定是哪个 IO,对于第三个参数,实际上我们确定了这个 IO 到底是复用为哪种功能之后,这个参数也很好选择,因为可选的参数在 stm32f4xx_gpio.h 列出来非常详细,如下

STM32F4嵌入式学习总结9

2、重点讲解解析GPIO_PinAFConfig

GPIO_PinAFConfig(GPIOA,GPIO_PinSource9,GPIO_AF_USART1)

void GPIO_PinAFConfig(GPIO_TypeDef* GPIOx, uint16_t GPIO_PinSource, uint8_t GPIO_AF)

{

uint32_t temp = 0x00;

uint32_t temp_2 = 0x00;


/* Check the parameters */

assert_param(IS_GPIO_ALL_PERIPH(GPIOx)); 1

assert_param(IS_GPIO_PIN_SOURCE(GPIO_PinSource)); 2

assert_param(IS_GPIO_AF(GPIO_AF)); 3


temp = ((uint32_t)(GPIO_AF) << ((uint32_t)((uint32_t)GPIO_PinSource & (uint32_t)0x07) * 4)) ; 4

(首先我们先看需要配置的参数为GPIOA,GPIO_PinSource9,GPIO_AF_USART1 ,GPIOA是基地址,GPIO_PinSource9的(0X09),PIO_AF_USART1(0x07),带入到GPIO_PinAFConfig,

1/2/3 assert_param();是预判函数,进入相应的I/O口,和功能预判。

4、temp = ((uint32_t)(GPIO_AF) << ((uint32_t)((uint32_t)GPIO_PinSource & (uint32_t)0x07) * 4)) ;

我们把第四条语句拆开,GPIO_Source这里是GPIO_PinSource9(0x09)于(uint32_t)0x07) 位与,结果是0x01,然后0x01*4,接下来temp= ((uint32_t)(GPIO_AF) <<0x04)即PIO_AF_USART1(0x07)带入temp=(0x70);

5、GPIOx->AFR[GPIO_PinSource >> 0x03] &= ~((uint32_t)0xF << ((uint32_t)((uint32_t)GPIO_PinSource & (uint32_t)0x07) * 4)) ;

化简后GPIOA->AFR[GPIO_Pin9>>0x03]&=~((uint32_t)0xF <<(uint32_t)0X01*4);

进一步化简:GPIOA->AFR[1]&=~((uint32_t)0XF<<0X04)相当于把GPIOA->AFR[1]=0000,复位初始化。

6、temp_2 = GPIOx->AFR[GPIO_PinSource >> 0x03] | temp;

化简:temp_2=GPIOA->AFR[1]|0X70,

第五步是把AF复用功能复位为6的复用功能进行一个清零,第6的结果是:temp_2=0X70;相当于GPIOA和AFR[1]建立联系。

7、GPIOx->AFR[GPIO_PinSource >> 0x03] = temp_2;

是将GPIOA->引脚9复用对应的功能。

结构体里AFR是定义为32位的结构数组


GPIOx->AFR[GPIO_PinSource >> 0x03]这里如果GPIO_PinSource为0-7,则在数组的AFR[0]里,如果GPIO_PinSource为8-16,则在数组的AFR[1]里,对应GPIOx_AFRL和GPIOx_AFRH。


GPIOx->AFR[GPIO_PinSource >> 0x03] &= ~((uint32_t)0xF << ((uint32_t)((uint32_t)GPIO_PinSource & (uint32_t)0x07) * 4)) ; 5

temp_2 = GPIOx->AFR[GPIO_PinSource >> 0x03] | temp; 6

GPIOx->AFR[GPIO_PinSource >> 0x03] = temp_2; 7

}



3、STM32F4的中断

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

STM32F40xx/STM32F41xx 总共有 92 个中断,STM32F42xx/STM32F43xx 则总共有 96 个中断,以下仅以 STM32F40xx/41xx 为例讲解。

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

STM32F4嵌入式学习总结9

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

ISER[8]:ISER 全称是:Interrupt Set-Enable Registers,这是一个中断使能寄存器组。上面说了 CM4 内核支持 256 个中断,这里用 8 个 32 位寄存器来控制,每个位控制一个中断。但是STM32F4 的可屏蔽中断最多只有 82 个,所以对我们来说,有用的就是三个(ISER[0~2]),总共可以表示 96 个中断。而 STM32F4 只用了其中的前 82 个。ISER[0]的 bit0~31 分别对应中断0~31;ISER[1]的 bit0~32 对应中断 32~63;ISER[2]的 bit0~17 对应中断 64~81;这样总共 82 个中断就分别对应上了。你要使能某个中断,必须设置相应的 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 中的中断分组设置来决定。这里简单介绍一下 STM32F4 的中断分组:STM32F4 将中断分为 5 个组,组 0~4。该分组的设置是由 SCB->AIRCR 寄存器的 bit10~8 来定义的。具体的分配关系如表

STM32F4嵌入式学习总结9

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

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

结合实例说明一下:假定设置中断优先级组为 2,然后设置中断 3(RTC_WKUP 中断)的抢占优先级为 2,响应优先级为 1。中断 6(外部中断 0)的抢占优先级为 3,响应优先级为 0。中断 7(外部中断 1)的抢占优先级为 2,响应优先级为 0。那么这 3 个中断的优先级顺序为:中断 7>中断 3>中断 6。

上面例子中的中断 3 和中断 7 都可以打断中断 6 的中断。而中断 7 和中断 3 却不可以相互打断!

通过以上介绍,我们熟悉了 STM32F4 中断设置的大致过程。接下来我们介绍如何使用函数实现以上中断设置,使得我们以后的中断设置简单化。

通过以上介绍,我们熟悉了 STM32F4 中断设置的大致过程。接下来我们介绍如何使用库函数实现以上中断分组设置以及中断优先级管理,使得我们以后的中断设置简单化。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”然后右键“Go to defition of …”可以查看到为:

#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))

这也是我们上表讲解的,分组范围为 0-4。比如我们设置整个系统的中断优先级分组值

为 2,那么方法是:

NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2);

这样就确定了一共为“2 位抢占优先级,2 位响应优先级”。

设置好了系统中断分组,那么对于每个中断我们又怎么确定他的抢占优先级和响应优先级呢?下面我们讲解一个重要的函数为中断初始化函数 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 中找到每个中断对应的名字。例如 USART1_IRQn。NVIC_IRQChannelPreemptionPriority:定义这个中断的抢占优先级别。NVIC_IRQChannelSubPriority:定义这个中断的子优先级别,也叫响应优先级。NVIC_IRQChannelCmd:该中断通道是否使能。

比如我们要使能串口 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();

展开阅读全文

页面更新:2024-03-09

标签:寄存器   优先级   全称   串口   初始化   内核   屏蔽   时钟   外设   嵌入式   函数   定义   参数   结构   功能   系统   科技

1 2 3 4 5

上滑加载更多 ↓
推荐阅读:
友情链接:
更多:

本站资料均由网友自行发布提供,仅用于学习交流。如有版权问题,请与我联系,QQ:4156828  

© CopyRight 2020-2024 All Rights Reserved. Powered By 71396.com 闽ICP备11008920号-4
闽公网安备35020302034903号

Top