一、如何使用stm32寄存器点灯?
1.1 寄存器映射表
寄存器本质就是一个开关,当我们把芯片寄存器配置指定的状态时即可使用芯片的硬件能力。
寄存器映射表则是开关的地址说明。对于我们希望点亮 GPIO_B 的一个灯来说,需要关注以下的两个寄存器:
1.2 配置时钟
对于我们实现希望点亮一个灯的需求来说,不仅需要配置配置 GPIO_B 的时钟,首先需要配置 GPIO_B 的时钟。
为什么需要先配置时钟呢?
STM32 外设通常都是给了时钟后才能设置它的寄存器(即才能使用这个外设)。STM32、LPC1XXX 等等都是这样,这么做的目的是为了省电,使用了所谓时钟门控的技术。寄存器是基于触发器的,触发器的赋值是一定需要时钟的,而寄存器的时钟是由总线时钟提供的,就是说没有总线时钟的话,你给寄存器值它是不会读入的。
对于下图中的系统框图来看,GPIO_B 挂载在 AHB 总线下 APB2 时钟。所以我们需要开启 APB2 的总线时钟。
STM32F10xxx 参考手册 6.3.7 告诉我们怎么使能 GPIO_B 的时钟。
由 1.1 的寄存器映射表图片来看,寄存器映射表可知
0x40000000(片上外设基地址) + 0x20000(AHB总线基地址) + 0x1000 (RCC外设基地址)
最后我们再加上 RCC_APBENR 的地址 0x18 即可成功访问这个寄存器。
对这个寄存器使用左移三位进行置位 IOPB 操作,这样就成功开启了 GPIO_B 时钟。
int main(void)
{
//片上外设基地址+AHB总线基地址+RCC外设基地址+RCC的AHB1时钟使能寄存器地址
*(unsigned int*)(0x40000000+0x20000+0x1000+0x18) |= ((1)<<3);
while(1);
}
// 函数为空,目的是为了骗过编译器不报错
void SystemInit(void)
{
}
值得注意的是这种写法,相当于向地址 0XFF 写入 0XFE。
*(unsigned int*)0xFE = 0XFF;
//等价于
unsigned int *p = 0xFE; //无符号 uint32_t 指针类型指向 0XFE 这个地址
*p = 0xFF; //解地址符 向这个地址写入0XFF
1.3 配置 GPIOB_0 模式
根据芯片手册提示 如果我们需要把 GPIOB_0 配置为输出高电平,只需要将 GPIO_CRL 第四位寄存器配置成 0001 即可,这代表了通用开漏输出模式,最大输出10MHz。
//片上外设基地址+APB2总线基地址+GPIOB外设基地址+GPIOB_CRL地址
*(unsigned int*)(0x40000000+0x10000+0x0C00+0x00) &= ~( (0xFF)<<(4*0) ); //低四位清零
*(unsigned int*)(0x40000000+0x10000+0x0C00+0x00) |= ( (1) << (4*0) ); //第四位搞成0001
1.4 配置 GPIOB_0 使其输出低电平
//片上外设基地址+APB2总线基地址+GPIOB外设基地址+GPIOx_ODR地址
*(unsigned int*)(0x40000000+0x10000+0x0C00+0x0C) &= ~(1<<0);
经过以上我们配置了 RCC_APB2ENR、GPIOB_CRL、GPIOB_ODR寄存器后。GPIOB_0 被配置成了开漏输出低电平。这样就可以点亮我们的灯泡了。
全部代码如下:
#include "stm32f10x.h"
int main(void)
{
//片上外设基地址+AHB总线基地址+RCC外设基地址+RCC的AHB1时钟使能寄存器地址
*(unsigned int*)(0x40000000+0x20000+0x1000+0x18) |= ((1)<<3);
//片上外设基地址+APB2总线基地址+GPIOB外设基地址+GPIOB_CRL地址
*(unsigned int*)(0x40000000+0x10000+0x0C00+0x00) &= ~( (0xFF)<<(4*0) );
*(unsigned int*)(0x40000000+0x10000+0x0C00+0x00) |= ( (1) << (4*0) );
//片上外设基地址+APB2总线基地址+GPIOB外设基地址+GPIOx_ODR地址
*(unsigned int*)(0x40000000+0x10000+0x0C00+0x0C) &= ~(1<<0);
while(1);
}
// 函数为空,目的是为了骗过编译器不报错
void SystemInit(void)
{
}
二、配置输入模式
我们希望将 GPIOA_0 配置成浮空输入,根据上表来配置寄存器。
/* PA0 key1引脚 */
//片上外设基地址+AHB总线基地址+RCC外设基地址+RCC的AHB1时钟使能PA寄存器地址
*(unsigned int*)(0x40000000+0x20000+0x1000+0x18) |= ((1)<<2);
//片上外设基地址+APB2总线基地址+GPIOA外设基地址+GPIOA_CRL地址
*(unsigned int*)(0x40000000+0x10000+0x0800+0x00) &= ~( (0xFF) << (4*0) );
*(unsigned int*)(0x40000000+0x10000+0x0800+0x00) |= ( (0X04) << (4*0) );
在最后一行,我们将GPIOA_CRL 寄存器低四位配置成 0100 是浮空输入。
全部代码如下:
#include "stm32f10x.h"
int main(void)
{
/* PB1 点灯引脚 */
//片上外设基地址+AHB总线基地址+RCC外设基地址+RCC的AHB1时钟使能PB寄存器地址
//打开PB时钟
*(unsigned int*)(0x40000000+0x20000+0x1000+0x18) |= ((1)<<3);
//片上外设基地址+APB2总线基地址+GPIOB外设基地址+GPIOB_CRL地址
//配置成PB1开漏输出
*(unsigned int*)(0x40000000+0x10000+0x0C00+0x00) &= ~( (0xFF)<<(4*0) );
*(unsigned int*)(0x40000000+0x10000+0x0C00+0x00) |= ( (1) << (4*0) );
//片上外设基地址+APB2总线基地址+GPIOB外设基地址+GPIOx_ODR地址
//默认给一个PB1高电平 灯灭
*(unsigned int*)(0x40000000+0x10000+0x0C00+0x0C) = 1;
/* PA0 key1引脚 */
//片上外设基地址+AHB总线基地址+RCC外设基地址+RCC的AHB1时钟使能PA寄存器地址
//打开PA时钟
*(unsigned int*)(0x40000000+0x20000+0x1000+0x18) |= ((1)<<2);
//片上外设基地址+APB2总线基地址+GPIOA外设基地址+GPIOA_CRL地址
//配置成PA0浮空输入
*(unsigned int*)(0x40000000+0x10000+0x0800+0x00) &= ~( (0xFF) << (4*0) );
*(unsigned int*)(0x40000000+0x10000+0x0800+0x00) |= ( (0X04) << (4*0) );
while(1){
//片上外设基地址+APB2总线基地址+GPIOB外设基地址+GPIOB_IDR地址
//读取是否按下按钮
if(*(unsigned int*)(0x40000000+0x10000+0x0800+0x08) & 1 != 0)
//片上外设基地址+APB2总线基地址+GPIOB外设基地址+GPIOx_ODR地址
//低电平 灯灭
*(unsigned int*)(0x40000000+0x10000+0x0C00+0x0C) &= ~(1<<0);
else
//片上外设基地址+APB2总线基地址+GPIOB外设基地址+GPIOx_ODR地址
//高电平 灯灭
*(unsigned int*)(0x40000000+0x10000+0x0C00+0x0C) = 1;
}
}
// 函数为空,目的是为了骗过编译器不报错
void SystemInit(void)
{
}
这样我们就实现了按下按钮点灯的操作。
本质上是等待 PA0 低电平后就把 PB1 拉低的程序。
三、中断
在图 EXTI功能框图 可以看到很多在信号线上打一个斜杠并标注“20”字样,这个表示在控制器内部类似的信号线路有20个, 这与EXTI总共有20个中断/事件线是吻合的。所以我们只要明白其中一个的原理,那其他19个线路原理也就知道了。
红色虚线指示的电路流程。它是一个产生中断的线路,最终信号流入到NVIC控制器内。这也是本篇中 GPIO 下降沿中断的线路。值得注意的是,图中的中断屏蔽/软件中断/下降沿等...是外设的寄存器,其最后需要传给 Cortex-M3 内核的 NVIC 寄存器进行中断优先级裁决,之后他会帮我们进行回调函数的操作。
绿色虚线指示的电路流程。它是一个产生事件的线路,最终输出一个脉冲信号。定时器等电机控制等使用的很多。在此不做描述。
3.1 中断线
EXTI有20个中断/事件线,每个GPIO都可以被设置为输入线,占用EXTI0至EXTI15, 还有另外七根用于特定的外设事件。
EXTI0 至 EXTI15 用于 GPIO,通过编程控制可以实现任意一个 GPIO 作为 EXTI 的输入源。
3.2 配置外设的寄存器
3.2.1 外部中断配置寄存器 1(AFIO_EXTICR1)
我们首先需要配置 AFIO_EXTICR 外部中断配置寄存器。使能 GPIOA_0 按钮引脚这条中断线的外部中断复用。因为我们是 GPIOA_0 所以自然配置 EXTIO[3:0] 这个寄存器就可以了。
如果我们还想让 GPIOB_0 也有中断能力呢?因为我们已经给GPIOA 用了,所以就不能做到了。
//片上外设基地址+APB2总线基地址+AFIO基地址+外部中断配置寄存器1(AFIO_EXTICR1)
*(unsigned int*)(0x40000000+0x10000+0x0000+0x08) = 0x00;
3.2.2 中断屏蔽寄存器(EXTI_IMR)
中断屏蔽器复位值是 0x0000 0000,也就是屏蔽所有中断,我们需要打开 MR0 的中断,使能 0 线的开启。
//片上外设基地址+APB2总线基地址+EXTI基地址+中断屏蔽寄存器(EXTI_IMR)
*(unsigned int*)(0x40000000+0x10000+0x0400+0x00) = 0x01;
3.2.3 上升沿触发选择寄存器(EXTI_RTSR)
//片上外设基地址+APB2总线基地址+EXTI基地址+上升沿触发选择寄存器(EXTI_RTSR)
*(unsigned int*)(0x40000000+0x10000+0x0400+0x08) = 0x01;
3.3 NVIC 配置
根据我们中断框图来看,在经过中断屏蔽等寄存器处理后,中断信号会传入到 NVIC 内。
在配置 Cortex-M3 内核的 NVIC 我们不再使用寄存器,为了方便而是使用 core_cm3.h 的库函数配置。
// 使用NVIC_EncodePriority函数编码中断优先级,并将结果存储在变量pri中
uint32_t pri = NVIC_EncodePriority(5, 0, 2);
// 设置EXTI0_IRQn(外部中断0)的中断优先级为之前编码的pri值
NVIC_SetPriority(EXTI0_IRQn, pri);
// 使能(启用)EXTI0_IRQn(外部中断0),使其能够在发生时被微控制器响应
NVIC_EnableIRQ(EXTI0_IRQn);
上述代码中我们使用 2 位抢占,2 位相应的配置方式。最后我们将 EXTI0 中短线配置为 0 级抢占 2级优先级方案。
四、全部代码
#include "stm32f10x.h"
#include "bsp_led.h"
#include "bsp_exti.h"
int main(void)
{
/* PB1 点灯引脚 */
//片上外设基地址+AHB总线基地址+RCC外设基地址+RCC的AHB1时钟使能PB寄存器地址
//打开PB时钟
*(unsigned int*)(0x40000000+0x20000+0x1000+0x18) |= ((1)<<3);
//片上外设基地址+APB2总线基地址+GPIOB外设基地址+GPIOB_CRL地址
//配置成PB1开漏输出
*(unsigned int*)(0x40000000+0x10000+0x0C00+0x00) &= ~( (0xFF)<<(4*0) );
*(unsigned int*)(0x40000000+0x10000+0x0C00+0x00) |= ( (1) << (4*0) );
//片上外设基地址+APB2总线基地址+GPIOB外设基地址+GPIOx_ODR地址
//默认给一个PB1高电平 灯灭
*(unsigned int*)(0x40000000+0x10000+0x0C00+0x0C) = 1;
/* PA0 key1引脚 */
//片上外设基地址+AHB总线基地址+RCC外设基地址+RCC的AHB1时钟使能PA寄存器地址
//打开PA时钟
*(unsigned int*)(0x40000000+0x20000+0x1000+0x18) |= ((1)<<2);
//片上外设基地址+APB2总线基地址+GPIOA外设基地址+GPIOA_CRL地址
//配置成PA0浮空输入
*(unsigned int*)(0x40000000+0x10000+0x0800+0x00) &= ~( (0xFF) << (4*0) );
*(unsigned int*)(0x40000000+0x10000+0x0800+0x00) |= ( (0X04) << (4*0) );
//片上外设基地址+APB2总线基地址+AFIO基地址+外部中断配置寄存器1(AFIO_EXTICR1)
*(unsigned int*)(0x40000000+0x10000+0x0000+0x08) = 0x00;
//片上外设基地址+APB2总线基地址+EXTI基地址+中断屏蔽寄存器(EXTI_IMR)
*(unsigned int*)(0x40000000+0x10000+0x0400+0x00) = 0x01;
//片上外设基地址+APB2总线基地址+EXTI基地址+上升沿触发选择寄存器(EXTI_RTSR)
*(unsigned int*)(0x40000000+0x10000+0x0400+0x08) = 0x01;
uint32_t pri=NVIC_EncodePriority(5,0,2);
NVIC_SetPriority(EXTI0_IRQn,pri);
NVIC_EnableIRQ(EXTI0_IRQn);
/* 等待中断,由于使用中断方式,CPU不用轮询按键 */
while(1)
{
}
}
void KEY1_IRQHandler(void)
{
//uint32_t a = *(unsigned int*)(0x40000000+0x10000+0x0C00+0x0C) ~= 1;
*(unsigned int*)(0x40000000+0x10000+0x0C00+0x0C) ^= 1;
EXTI_ClearITPendingBit(KEY1_INT_EXTI_LINE);
}
/*********************************************END OF FILE**********************/