一,端口复用
stm32的很多内置外设都是和GPIO复用的。如串口一的引脚对应IO为PA9和PA10,这两个IO口默认为GPIO引脚,而当我们将其作为串口一的RX和TX引脚使用的时候,就称为端口复用。
复用端口初始化有几个步骤:
1) GPIO 端口时钟使能。
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE);
2) 复用的外设时钟使能。
RCC_APB2PeriphClockCmd(RCC_APB2Periph_USART1, ENABLE);
3) 端口模式配置。 在 IO 复用位内置外设功能引脚的时候,必须设置 GPIO 端口的模式。
要初始化GPIO和复用外设功能。
串口模式和GPIO模式的对应关系是:
USART引脚 | 配置 | GPIO配置 |
USARTx_TX | 全双工模式 | 推挽复用输出 |
USARTx_TX | 半双工同步模式 | 推挽复用输出 |
USARTx_RX | 全双工模式 | 浮空输入或带上拉输入 |
USARTx_RX | 半双工同步模式 | 未用,可作为通用IO |
例如:
//USART1_TX PA.9 复用推挽输出
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_9; //PA.9
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP; //复用推挽输出
GPIO_Init(GPIOA, &GPIO_InitStructure);
//USART1_RX PA.10 浮空输入
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_10;//PA10
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IN_FLOATING;//浮空输入
GPIO_Init(GPIOA, &GPIO_InitStructure);
二,重映射
一个 外设的引脚除了具有默认的端口外,还可以通过设置重映射寄存器的方式,把这个外设的引脚 映射到其它的端口。
例如,我们由重映射表可知,串口复用的TX和RX可以重映射到PB6和PB7上。
所以我们在重映射时不仅要使能GPIO时钟和串口时钟(复用的外设),还要使能AFIO时钟,然后调用重映射函数。
1)使能 GPIOB 时钟: RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB, ENABLE);
2)使能串口 1 时钟: RCC_APB2PeriphClockCmd(RCC_APB2Periph_USART1, ENABLE);
3)使能 AFIO 时钟: RCC_APB2PeriphClockCmd(RCC_APB2Periph_AFIO, ENABLE);
4)开启重映射: GPIO_PinRemapConfig(GPIO_Remap_USART1, ENABLE);
三,中断优先级管理
中断寄存器:
1)ISER[8]这是一个中断使能寄存器组。
CM3 内核由256个中断,由8个32位寄存器控制,但STM32F103只用了其中的60个,则我们只需用到ISER[0],ISER[1].我们可以设置对应的ISER位为1,给对应的中断使能.
2)ICER[8]:,是一个中断除能寄存器组。作用与ISER相反,给对应的ICER位置1,则取消相应位的使能。
注意:NVIC的寄存器都是写1有效,写0无效。
3)ISPR[8]:是一个中断挂起控制寄存器组。每个位 对应的中断和 ISER 是一样的。通过置 1,可以将正在进行的中断挂起,而执行同级或更高级别 的中断。
4)ICPR[8]:是一个中断解挂控制寄存器组。其作 用与 ISPR 相反,对应位也和 ISER 是一样的。通过设置 1,可以将挂起的中断接挂.
5)IABR[8]:,是一个中断激活标志位寄存器组。对应位 所代表的中断和 ISER 一样,如果为 1,则表示该位所对应的中断正在被执行。这是一个只读寄 存器,通过它可以知道当前在执行的中断是哪一个。在中断执行完了由硬件自动清零。
6)IP[240]:是一个中断优先级控制的寄存器组。STM32 的中断分组与这个寄存器组密切相关。IP 寄存器组由 240 个 8bit 的寄 存器组成,每个可屏蔽中断占用 8bit,这样总共可以表示 240 个可屏蔽中断。而 STM32 只用到 了其中的前 60 个。IP[59]~IP[0]分别对应中断 59~0。而每个可屏蔽中断占用的 8bit 并没有全部 使用,而是 只用了高 4 位。这 4 位,又分为抢占优先级和子优先级。抢占优先级在前,子优先 级在后。而这两个优先级各占几个位又要根据 SCB->AIRCR 中的中断分组设置来决定。
stm32中断分组:
组 | AIRCR[10:8] | bit[7:4]分配情况 | 分配结果 |
0 | 111 | 0:4 | 0 位抢占优先级,4 位响应优先级 |
1 | 110 | 1:3 | 1位抢占优先级,3位响应优先级 |
2 | 101 | 2:2 | 2位抢占优先级,2位响应优先级 |
3 | 100 | 3:1 | 3 位抢占优先级,1 位响应优先级 |
4 | 011 | 4:0 | 4 位抢占优先级,0 位响应优先级 |
例如组设置为 3,那么此时 所有的 60 个中断,每个中断的中断优先寄存器的高四位中的最高 3 位是抢占优先级,低 1 位是 响应优先级。每个中断,你可以设置抢占优先级为 0~7,响应优先级为 1 或 0。抢占优先级的 级别高于响应优先级。而数值越小所代表的优先级就越高。
这里需要注意两点:第一,如果两个中断的抢占优先级和响应优先级都是一样的话,则看 哪个中断先发生就先执行;第二,高优先级的抢占优先级是可以打断正在进行的低抢占优先级 中断的。而抢占优先级相同的中断,高优先级的响应优先级不可以打断低响应优先级的中断。
之后是库函数的讲解,首先要讲解的是中断优先级分组函数 NVIC_PriorityGroupConfig,其函数申明如下: void NVIC_PriorityGroupConfig(uint32_t NVIC_PriorityGroup);这个函数的作用是对中断的优先级进行分组,这个函数在系统中只能被调用一次,一旦分组确定就最好不要更改。这个函数唯一目的就是通过设置 SCB->AIRCR 寄存器来设置中断优先级分组。
比如我们设置整个系统的中断优先级分组值 为 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 寄存